当前位置: 首页 > news >正文

献县城市住房建设局网站青柠影视在线观看免费高清电视剧

献县城市住房建设局网站,青柠影视在线观看免费高清电视剧,推广公司app主要做什么,百度seo排名培训优化文章目录 基础认识#xff1a;语言特性#xff08;面向对象编程#xff09;#xff1a;c的类#xff08;相当于c中的结构体#xff09;#xff1a;三大特性#xff1a;c包含四种编程范式#xff1a;优缺点#xff1a; c程序编译的过程#xff1a;预处理-编译语言特性面向对象编程c的类相当于c中的结构体三大特性c包含四种编程范式优缺点 c程序编译的过程预处理-编译优化、汇编-链接编译型语言-可执行程序源代码的组织生成可执行文件的步骤预处理 头文件展开、去注释、宏替换、条件编译等包含的头文件#include宏定义#define条件编译#ifdef、#ifndef 编译只有源文件.cpp才能编译链接 更多细节 计算机体系中的存储层级内存堆、栈的不同用途和区别动态内存分配的注意事项可移植性进程在内存空间中的布局API 基础语法命名空间namespace{...}常用的数据结构及其内存分配变量与数据类型基本数据类型整型浮点型字符型字符charc风格的字符串string不是基本数据类型关于字符的表示问题即将字符与相应的数字对应起来 布尔类型 bool无值型 void 非基本数据类型数组 type[ ]动态创建数组一维数组二维数组三维数组 指针 type *指针变量二级指针空指针野指针函数指针 引用 type 创建引用的语法引用用于函数的参数引用用于函数返回值 类 class / 结构体 struct类/结构体数据对齐的问题类/结构体内存布局结构体中的全部成员清零复制结构体结构体指针结构体数组结构体中的指针 联合体 union枚举 enum符号常量 #define 或 constc11新增的long long类型自动推导类型void关键字零初始化 类型转换c的类型转化c的类型转换自动/隐式类型转换强制类型转换static_cast最常用reinterpret_castdynamic_castconst_cast总结 静态变量 静态、动态分配内存动态内存分配动态内存分配的注意事项堆、栈的不同用途和区别实现C语言c语言nullptr 动态分配内存的布局malloc与free的实现原理被free回收的内存是立即返归还操作系统吗 静态内存分配内存泄露的问题 数据的输入输出控制c的关键字c的运算符结构体、类结构体 静态对象与全局对象的构造顺序函数/类中的静态对象全局对象的构造顺序 临时对象深、浅拷贝的问题左值/右值、左值引用/右值引用、万能引用、move、移动语义、完美转发左值、右值 左、右值区别c11中扩展了右值的概念分为纯右值、将亡值使用左值的运算符不是左值就是右值 引用类型c98中均为左值引用c11开始出现右值引用左值引用lvalue reference绑定到左值即给左值起别名右值引用rvalue reference绑定到右值即给右值起别名总结 move函数c11标准库中的新函数万能引用T存在的前提为模板参数类型、const T移动语义完美转发 函数新特性、函数重载、inline内联函数、函数中const的使用、递归函数函数新特性函数的使用细节函数重载const用法递归函数 c中的I/O流、I/O缓存区I/O流I/O缓存区 文件操作std::move()和std::ref的对比 基础认识 语言特性面向对象编程 c的类相当于c中的结构体 定义类的过程也被称为定义对象的过程类可以像结构体一样定义成员变量还可以定义该类的函数方法把功能包在类中需要时通过定义一个对象来调用程序即基于对象的程序设计继承性继承父类后可以增加新的方法、多态性升华了基于对象程序设计故称为面向对象程序设计。易扩展、易维护、模块化通过设置各种级别来限制访问维护数据安全 三大特性 封装数据和代码捆绑在一起避免外界干扰和不确定性访问封装可以使代码模块化。继承可以通过继承父类的数据和方法也可以新增、修改继承来的方法重写和重载从而提高程序的复用性。多态就是让具有继承关系的不同的类对象可以调用同名的成员函数并产生不同的响应结果即多态的目的是接口重用。 静态多态编译期函数重载动态多态运行期虚函数重写 c包含四种编程范式 面向过程、面向对象、泛型编程、函数式编程lambda表达式。 优缺点 优点具有强大的抽象封装能力、高性能、低功耗。c相比于C语言有类、虚函数、标准库 缺点语法相对复杂学习曲线比较陡需要一些好的规范和范式否则代码很难维护。 c程序编译的过程预处理-编译优化、汇编-链接 编译型语言-可执行程序 c要生成一个可执行文件需要将 .cpp 经过编译、链接。每个 .cpp 文件经过编译后对应一个.obj文件linux对应的是.o文件将各个.obj文件链接起来就是.exe可执行文件。 源代码的组织 头文件.h#include头文件、函数声明、结构体声明、类声明、模板的声明和定义、内联函数、#define和const定义的常量等。源文件.cpp#include***.h头文件、函数的定义、类定义。主程序main#include需要的头文件实现主程序和框架。 生成可执行文件的步骤 预处理 头文件展开、去注释、宏替换、条件编译等 预处理的指令有三种包含的头文件#include宏定义#define定义宏、#undef删除宏条件编译#ifdef、#ifndef。 包含的头文件#include #include...直接从编译器自带的函数库的目录中寻找文件。#include...先从自定义的目录中寻找文件找不到再从编译器自带的函数库目录中寻找。 注意编译器会将头文件的内容复制到包含头文件的文件中。 宏定义#define 编译时编译器会将程序中的宏名用宏内容替换即宏展开。 无参数的宏#define 宏名 宏内容。 有参数的宏#define Max(x,y) ((x)(y) ? (x):(y))。 c中内联函数inline可以替代有参数的宏且效果更好。 c 中常用的宏 _FILE_当前源代码的文件名即绝对路径 _FUNCTION_当前源代码的函数名 _LINE_当前源代码的行号 _DATE_编译日期 _TIME_编译时间 _TIMESTAMP_编译时间戳 _cplusplusc程序编译时该宏就会被定义 条件编译#ifdef、#ifndef #ifdef 宏名 // 如果宏名存在则执行程序段一否则执行程序段二程序段一 #else程序段二 #endif#ifndef 宏名 // 如果宏名不存在则执行程序段一否则执行程序段二程序段一 #else程序段二 #endif在c使用预编译指令#include时为了防止头文件重复包含即头文件防卫式声明两种方式 用#ifndef指令受c语言标准的支持可以针对文件中的部分代码用#pragma once指令放在文件开头有些编译器不支持只能针对整个文件但效率更高 注意这种方法仅仅对单个.cpp文件有效不是整个项目即只是在编译时防止了重定义。但可能出现链接时的重定义。 编译只有源文件.cpp才能编译 将预处理生成的文件经过词法分析、语法分析、语义分析以及优化和汇编后编译成若干个目标文件二进制文件。 链接 将编译生成的目标文件以及他们所需要的库文件链接起来生成可执行文件。 更多细节 分开编译的优点每次只编译修改过的源文件然后再链接效率更高 编译单个.cpp文件只需知道所用到的变量/函数/类的名称的存在即可不会将它们的定义一起编译 如果函数和类的定义不存在编译不会报错但链接会出现无法解析的错误 链接时变量、函数和类的定义只能有一个否则会出现重定义的错误 如果把变量、函数、和类的定义放在.h文件中.h被多次包含链接前会存在多个副本在不同的.cpp文件中则链接时会出现重定义错误 如果将变量、函数、类的定义放在.cpp文件中.cpp文件只会被编译一次链接前不会重复包含故不会报错 尽可能不使用全局变量如果一定要使用需要在.h文件中声明且要加 extern 关键字在.cpp文件中定义。 全局的 const 变量在头文件中定义且const 变量仅仅对单个文件内有效。 全局 const 变量和全局变量的区别 作用域不同 1全局 const 常量只对本文件内有效。 2全局变量对所有 #include 头文件的文件有效。 定义方式不同 1全局变量需要在.h文件中声明并加extern关键字在.cpp文件中定义。 2const全局常量直接在本文件中声明和定义。 到底怎么样才能避免重复定义呢 关键是要避免重复编译 防止头文件重复包含是有效避免重复编译的方法即不要将同一个.h文件在多个文件中#include。 但最好的方法还是 头文件尽量只有声明不要有定义。这么做不仅仅可以减弱文件间的编译依存关系减少编译带来的时间性能消耗更重要的是可以防止重复定义现象的发生防止程序崩溃。 函数模板 和 类模板的声明和定义要放在同一个.h文件中。 函数模板 和 类模板的特化版本的代码是真实的定义要放在.cpp文件中。 计算机体系中的存储层级 内存 能存储的比特数取决于集成电路里的元器件的数目。 内存中的资源会被操作系统进行调用分配给正在执行的程序 1操作系统会给自己预留一部分内存资源 2其余的由其他正在执行的程序进行分配 程序只能在操作系统分配给它的范围内使用内存 全局变量、程序代码分配在静态内存区域即从开始到结束这些内存区域都被占用 程序在运行时可以向操作系统动态的申请和释放一些内存堆内存)。 局部变量、函数参数返回值等被分配在栈内存区域即函数调用栈 函数每一次被调用时在函数调用栈中分配一个大小合适的栈帧存储这一次的局部变量、参数和返回值)。在函数返回时释放栈帧的内存。 注意递归过深会导致程序崩溃是因为大量的栈帧未释放占满了函数调用栈的内存即stack overflow。 堆、栈的不同用途和区别 不同用途 栈空间有限编译器自动分配速度较快。堆只要不超过实际的物理内存而且在操作系统能分配的最大内存大小内都可以分配分配速度慢通过malloc/free、new/delete来实现。 区别 管理方式不同栈是自动管理的出作用域将被释放堆需要手动释放否则可能引发内存泄漏空间大小不同堆的空间大小受限于物理内存空间栈就小的可怜只有8M可修改系统参数分配方式不同堆是动态分配的需手动释放栈是静态分配和动态分配但都是自动释放分配效率不同栈是系统提供的数据结构由计算机底层支持进出栈有专门的指令效率较高堆是由c函数库提供的是否产生碎片栈是严格按照先进后出LIFO顺序不会产生碎片堆频繁的随意分配和释放会造成内存空间的不连续故容易产生碎片太多碎片会导致性能下降增长方向不同栈向下增长以降序分配内存地址堆向上增长以升序分配内存地址 动态内存分配的注意事项 动态分配的内存没有变量名只能通过指向它的指针来操作内存中的数据 实现 1C语言通过malloc/free从堆区申请和释放内存 void* malloc(int NumBytes) // NumBytes是要分配的字节数 // 分配成功返回指向被分配内存的指针即返回一个地址分配失败返回空地址NULL// 当不使用这段内存时要用free函数将这段内存释放并被系统回收需要时再重新分配 void free(*FirstBytes)eg. 给申请的100个整型内存空间赋值 // 分配400个字节 int *ptr (int *)malloc(100*sizeof(int)); if (ptr ! NULL) {// 通过指针ptr1给指向ptr的内存空间赋值int *ptr1 ptr;for (int i 0; i 100; i){*ptr1 int(i); // 等价于*(ptr1 i) i;}// 输出申请的100个整型内存空间的值if (ptr1 ! nullptr) // NULL和nullptr实际上是不同的类型尽量在涉及指针时能用nullptr就用{for (int i 0; i 100; i){cout *(ptr i) ; }cout endl;}// 释放申请的内存// ptr NULL;// delete ptr;free(ptr); }2c用new/delete运算符标识符分配和释放在堆区的内存 // new使用的一般格式 指针变量名 new 类型标识符; 指针变量名 new 类型标识符(初始值); 指针类型名 new 类型标识符[内存单元的个数];// delete使用的一般格式 new的时候用[ ]则delete就必须加[ ]不用写数组的大小; 注意如果动态分配的内存不再使用了必须delete释放它否则可能耗尽系统的内存。 可移植性 编译性语言编译为二进制文件可执行文件执行速度快。 1先将源文件逐个编译compile为.obj二进制目标文件链接link后生成二进制.exe可执行文件。 2源程序 - 编译器 - 目标程序 - 链接器 - 可执行程序 解释性语言不进行编译先解释再运行如python。 进程在内存空间中的布局 当可执行文件被加载到内存之后就变成了一个进程。 进程的虚拟地址空间 栈堆栈/栈区地址由高向低生长局部变量每次执行程序时该变量的地址都会发生变化编译时期即可确定变量的范围作用域是{}。 windows系统默认的栈区的大小是1M、Linux默认的栈区的大小是8M / 10M。 堆区地址由低到高生长new、malloc等申请的内存空间需要在运行阶段才能确定变量大小的范围作用域是整个程序范围内。 所有系统的堆空间的上限接近内存虚拟内存的总大小的除了一部分被OS占用。 数据段全局变量已初始化的全局变量和BSS段未初始化的全局变量、静态成员变量、全局函数的入口地址。 一些全局量全局变量、全局函数、类静态成员变量等的地址值在生成可执行文件时已经确定好了不会改变存放在bss段、数据段等一旦加载映射到内存时这些地址值都不会发生变化 代码段存放程序执行代码的一块内存区域。 API 操作系统预先把这些复杂的操作写在一个函数里面编译成一个组件一般是动态链接库随操作系统一起发布并配上说明文档程序员只需要简单地调用这些函数就可以完成复杂的工作。 这些封装好的函数就叫做APIApplication Programming Interface即应用程序编程接口。 C语言 API 以函数的形式呈现。C 是在C语言的基础上进行的扩展所以 C API 既包含函数也包含类。 基础语法 命名空间namespace{...} 作用为了防止名字冲突而引入的一种机制。 命名空间分割了全局空间每个命名空间可以看作一个作用域可以在不同的命名空间中定义同名的类、函数、模板、变量等。 命名空间的定义可以不连续甚至可以在多个文件中可以在同一个或不同的.cpp文件中通过打开namespace添加新的成员函数。 命名空间中类、函数、模板、全局变量等的分文件编写与不使用命名空间的做法相同。 调用格式 // 1、在同一个.cpp文件中 namespace 命名空间名 {// 类、函数、模板、变量的声明和定义 }命名空间名::实体名// 2、在不同的.cpp文件中 using namespace 命名空间名;命名空间名::实体名注意 命名空间中声明全局变量而不是使用外部全局变量和静态变量 对于using声明首选将其作用域设置为局部而不是全局 namespace {int a 10;}不要在头文件中使用#using编译指令非要使用应该其放在所有的#include之后 匿名命名空间从创建的位置到程序结束都是有效的且仅仅可以在当前文件中直接使用 namespace {int a 10;}int main() {cout a endl; }常用的数据结构及其内存分配 变量一块具有类型的内存类型数据存储的表示方式以及你可以对它进行的操作指针一个内存的地址指针的类型可能说明该指针指向的特定类型的变量void*可以指向任何特定类型的变量引用可以理解为一种“语法糖”左值引用/右值引用数组内存中连续排列的多个同类型变量数组名称可以作为指向第一个元素的指针自定义类型class/struct一组成员变量在内存里的排列方式以及可以对它进行的操作对象按照特定排列方式存储在内存里的一组成员变量 变量与数据类型 变量是在程序执行过程中可以改变的量即代表一块内存区域修改变量值会引起内存区域中内容的改变 变量名标识内存中的一个具体的存储单元即地址方便操作这段内存 数据类型决定变量分配空间的大小。 基本数据类型 整型 有符号整型short2 bytes、int4 bytes、long4 bytes。 机器数 ! 真值补码形式 -3 机器数10000000 00000000 00000000 00000011 真值11111111 11111111 11111111 11111101 3 机器数00000000 00000000 00000000 00000011 真值00000000 00000000 00000000 00000011 补码形式 负数的补码正数的补码 - 按位取反后1 正数的补码还是正数 无符号整型unsigned short、unsigned int、unsigned long。 浮点型 实型 float 4bytes 、双精度 double 8bytes 字符型 字符char 用单引号引起来的一个字符如字符型常量 ‘a’占用一个字节存放 a。 转义字符‘\\n’ 、‘t’、 ‘\\’。 char [] 和 char*的区别 地址和地址存储的信息 char* str hello world指向的是字符串常量会存储在全局区。 char str[11] {hello world}存储在栈区。 可变和不可变 char*指向的常量可以改变但常量中的内容不能改变具体的还要看char*指向的存储区域是否可变。 char []中的内容可以改变但整体变量不能改变。 c风格的字符串 #define CRT_SECURE_WARNINGS #include iostream #include cstring using namespace std;struct Stu {char* name; }; int main() {Stu stu;// 使用memset函数将stu.namenullptr置空memset(stu, 0, sizeof(Stu)); stu.name new char[21];char* name (char*)yoyoll;strncpy(stu.name, name, sizeof(name));cout stu.name endl; delete[] stu.name;stu.name nullptr;return 0; }c语言中如果字符型char数组的末尾包含了空字符’\0’即0那数组中的内容就是一个字符串。 由于字符串必须以\0结尾故声明时要预留1个字节的位置如char str[21]只能存放20个字符。 // 清空字符串void* memset(void* buffer, int ch, size_t count); char name[20]; memset(name, 0, sizeof(name)); // 会将字符串name中的所有字符置为0即字符\0// 字符串的复制或赋值 char* strcpy(char* dest, const char* src); // 将src指向的字符串拷贝到dest所指的地址复制完字符串后在dest尾加\0 // 注意如果dest指向的内存空间不够大则会导致数组越界 char* strncpy(char* to, const char* from, size_t count ); // 将 字符串from 中至多count个字符复制到 字符串to // 如果 字符串from 的长度小于count其余部分用\0填补长度大于count则只会截取前count个字符且不会在dest后追加\0// 获取字符串的长度 size_t strlen(const char* str); // 区分strlen(str)返回字符串str的字符数而sizeof(str)返回字符串str的字节数。// 字符串的拼接 char* strcat(char*dest, const char* src); // 注意如果dest指向的内存空间不够大则会导致数组越界 char* strncat(char* dest, const char* src, const size_t n);注意 处理字符串时会从起始位置开始搜索直到找到’\0’即0为止不会判断是否越界因大部分函数用char*作为字符串的形参故无法获取字符串的长度只知道字符串的起始地址和其以’\0’结尾 字符串每次使用前都要初始化三种初始化的方式导致的不同 char* constPtr helloptr是一个字符串指针hello被存放在常量区不可修改 char charArr[] hellocharArr是一个字符串数组存放在栈区可修改 char* charPtr (char*)malloc(sizeof(6)); strcpy(charPtr, hello); 即hello被存放在堆区可修改 VS中如果要使用c标准的字符串操作函数要在源代码前加#define _CRT_SECURE_NO_WARNINGS string不是基本数据类型 c中string类是封装了c风格的字符串c的字符串string中有一个指向动态分配的内存地址指针 c11中的原始字面量可以直接表示字符串的实际含义且不需要转义和连接语法R(字符串的内容)、R***(字符串的内容)*** 注意 Visual Studio中未初始化的栈空间用0xCC填充而未初始化的堆空间用0xCD填充。0xCCCC和0xCDCD在中文GB2312编码中分别对应“烫”字和“屯”字。如果一个字符串没有结束符’\0’输出时就会打印出未初始化的栈或堆空间的内容就会出现“烫烫烫”、“屯屯屯”乱码。 关于字符的表示问题即将字符与相应的数字对应起来 ASCII码 1基于拉丁字母的一套电脑编码系统主要用于显示现代英语和其他西欧语言。 2使用指定的7或8位二进制数组合成的0127和0255的十进制数字表示可能的字符。 Unicode编码 最初的目的是将世界上的文字都映射到一套字符空间中并转化成相应的数字存储起来 为了表示Unicode字符集有3种确切的说是5种Unicode的编码方式 UTF-8 11 byte表示一个字符可以兼容ASCII码 2特点存储效率高变长不方便内部随机访问无字节序问题可作为外部编码 UTF-16 12 bytes表示一个字符有 UTF-16BEbig endian、UTF-16LElittle endian 2特点定长方便内部随机访问有字节序的问题不可作为外部编码。 UTF-32 14 bytes表示一个字符有UTF-32BEbig endian、UTF-32LElittle endian 2特点定长方便内部随机访问有字节序的问题不可作为外部编码。 编码错误的根本原因编码方式和解码方式的不统一 布尔类型 bool 1bytes 无值型 void 0bytes 非基本数据类型 数组 type[ ] 动态创建数组 使用new 数据类型[]动态创建数组时需要用delete[] 数组名来释放动态分配的内存空间。 new分配内存时如果内存不足会报错导致程序中止。 在new关键字后添加std::nothrow选项后则返回的是nullptr并不会产生异常。 delete[]中不需要指定数组的大小系统会自动跟踪已分配数组的内存。 声明数组时如果数组的长度是变量相当于在栈上动态分配数组并且不需要释放。 一维数组 初始化 数据类型 数组名[大小]{ val1, val2, ... } 数据类型 数组名[大小]{ 0 }; // 初始化所有变量为0数组的本质 数组一段连续的内存空间且数组名表示该段连续内存的首地址即数组第0个元素的地址指针的值是可以修改的除了常量指针和常量常指针但数组名是常量不可修改 数组的指针表示法 c编译器的解释地址名[下标]即(地址名下标) 数组名[下标]即*(数组名下标) 举例(arr[2])[2] -- arr[4]、char arr[10]; char* ptr arr; cout *(ptr i) endl;。 // 清空数组最常用来初始化清空一个字符串 void* memset(void* s, int val, size_t bytes_num);// 拷贝数组 void* memcpy(void* dest, void* src, size_t bytes_num);// 数组的排序qsort快速排序 void qsort(void *base, int nelem, int width, int (*fcmp)(const void* p1, const void* p2)); /* qsort函数中第四个参数回调函数决定了排序的顺序返回值 0p1会排在p2的前面返回值 0p1和p2的顺序不确定返回值 0p2会排在p1的前面 注意回调函数中的void*必须具体化即转化为具体的数据类型才能使用。qsort()函数中为什么要传入第三个参数 答因为qsort不知道数数组的具体类型故在“回调函数内部是通过内存块操作数据”的交换两个数据是通过memcpy()函数实现的而不是数据类型。 */#include iostream #include stdio.h #include stdlib.h using namespace std;void Print(int* ptr, size_t size) {if (ptr nullptr) { return; }for (int i 0; i size; i){cout *(ptr i) ,;}cout endl; }/*返回值 0p1会排在p2的前面返回值 0p2会排在p1的前面返回值 0p1和p2的顺序不确定 */ int cmpAsc(const void* p1, const void* p2) {return (*(int*)p1 - *(int*)p2); } int cmpDesc(const void* p1, const void* p2) {return (*(int*)p2 - *(int*)p1); }int main(int argc, char *argv[]) {int arr[10] { 1,4,5,0,2,9,3,7,6,8 };qsort(arr, sizeof(arr) / sizeof(int), sizeof(int), cmpAsc);size_t size sizeof(arr) / sizeof(int);Print(arr, size);qsort(arr, sizeof(arr) / sizeof(int), sizeof(int), cmpDesc);Print(arr, size);return 0; }二维数组 在内存中是以行优先的形式存放在连续的内存空间中的。 可用一维数组的方法查看二维数组只需二维数组的首地址和大小即可。 #include iostream #include cstring using namespace std;int main() {int m 2; int n 3;int arr[m][n];memset(arr, 0, sizeof(arr));arr[0][2] 1; arr[1][2] 2;int* ptr (int *)arr;for (size_t i 0; i 6; i){cout *(ptr i) ,;}return 0; }二维数组用于函数形参列表 // 行指针 数据类型 (*行指针名)(行大小) 一维数组名; // 行大小即数组长度一维数组名即数组的地址也是行地址 int arr[2][3]; int(*p)[3] arr; // arr是二维数组的首地址即0号元素的地址 // 将二维数组传递给函数 void func(int(*p)[3], ...); void func(int p[][3], ...);三维数组 int arr3D[2][3][4]; memset(arr3D, 0, sizeof(arr3D));int (*p)[3][4] arr3D; void func(int(*p)[3][4], ...);指针 type * 指针变量 简称指针是一种特殊的变量专用于存放变量在内存中的起始地址。 语法数据类型 *变量。 对指针的赋值 任何数据类型的地址都是以十六进制存储在内存中的指针变量 变量不同的指针存放不同类型变量的地址 指针占用的内存 指针也是变量故需要占用内存64位操作系统中指针变量占用的都是8 bytes指针存放变量的地址指针名表示的就是该地址就像变量名表示变量的值一样*解引用用于指针可以获取该地址中的值。 使用指针的两个目的 // 传递地址 int* p; // 整型指针 int* p[3]; // 一维整型指针数组元素是3个整型指针p[0]、p[1]、p[2] int(*p)[3]; // 一维整型数组指针用于指向数组长度是3的整型数组 int* p(); // 返回值类型是整型的函数p的地址 int(*p)(int, int); // p是函数指针函数返回值是整型int// 存放动态分配的内存地址 int* p new int(3);二级指针 指针用于存放普通变量的地址二级指针用于存放指针变量的地址。 #includeiostream using namespace std;int main() {int* p 0;{int** pp p;*pp new int(3);cout pp , *pp endl;}cout p , *p endl;return 0; }空指针 声明指针后赋值前指针指向空即没有任何地址。 对空指针进行解引用程序会崩溃。 函数中应该有判断形参是否为空指针的代码目的是保证程序的健壮性。为何访问空指针会出现异常 NULL指针分配的分区范围是0x00000000 ~ 0x0000FFFF该段是空闲的空间且没有相应的物理存储器与之对应。对该段的空间的任何操作都会引发异常。需要人为的划分一个空指针区域即NULL指针分区。 对空指针使用delete运算符系统会忽略该操作不会出现异常。内存被释放后应将该指针指向空。 注意c11建议用nullptr表示空指针也就是(void*)0NULL当作0使用。 野指针 野指针指向的是非有效的地址故访问的时候程序可能会崩溃。 出现野指针的情况主要有三种 指针在定义的时候如果没有初始化它的值是不确定的乱指的故如果指针初始化时不知道指向哪就指向nullptr。如果用指针指向了动态分配的内存内存被释放后指针不会置空但指向的地址是无效的故动态分配的内存被释放后需要将其置空nullptr。指针指向的变量已超越变量的作用域即变量的内存空间已经被系统回收故函数不要返回局部变量的地址。 野指针的危害比空指针大故需要避免否则会造成程序的不稳定。 函数指针 函数的二进制代码放在内存分区的代码段函数的地址是其在内存中的首地址。 使用函数指针步骤 // 声明函数指针 int(*funcPtr)(int, int)// 让函数指针指向函数的地址 int maxValue(int val1, int val2) { return (val1 val2 ? val1 : val2) }; funcPtr maxValue;// 通过函数指针调用函数 int res funcPtr(5, 1) // 或 (*funcPtr)(5,1);主要用于给函数传递函数指针作为参数并在函数内部使用该函数指针达到调用该函数的目的。 #include iostream using namespace std;template typename T bool ascending(T x, T y) {return x y; }template typename T bool descending(T x, T y) {return x y; }templatetypename T void bubblesort(T* a, int n, bool(*cmpfunc)(T, T)ascending){bool sorted false;while(!sorted){sorted true;for (int i0; in-1; i){if (cmpfunc(a[i], a[i1])) {std::swap(a[i], a[i1]);sorted false;}n--;} }int main() {int a[8] {5,2,5,7,1,-3,99,56};int b[8] {5,2,5,7,1,-3,99,56};bubblesortint(a, 8, ascending);for (auto e:a) { cout e };cout endl;bubblesortint(b, 8, descending);for (auto e:b) { cout e };return 0; } // -3 1 2 5 5 7 56 99 // 99 56 7 5 5 2 1 -3 引用 type 使用指针存在的问题空指针、野指针、容易改变指针指向的值却在继续使用。 引用是c新增的复合类型是指针常量不允许修改指针的指向的伪装“引用int ra a 指针常量int* const rb a”。 int x1 2; int x2 3;// 引用使用时必须初始化而且一个引用永远指向它初始化的那个对象 int x3 x1; cout x1 , x3 endl;x3 x2; cout x1 , x3 endl; 使用引用则不存在空引用、必须初始化、一个引用永远指向它初始化的那个对象。引用可以认为是变量别名修改引用的值同时也会改变原变量的值。 函数传递参数的说明 对内置基础类型而言在函数中传递时pass by value更高效对面向对象中自定义类型而言在函数传递中pass by reference to const更高效 疑问 有了指针为什么还需要引用为了支持运算符重载。有了引用为什么还需要指针为了兼容c语言。 创建引用的语法 数据类型 引用名 原变量名 引用名 和 原变量名的数据类型、值、内存单元相同必须要在声明引用的时候初始化且初始化后不可改变 引用用于函数的参数 #include iostream #include string using namespace std; void funcByValue(int age, string name) {age 21;name wowo; } void funcByQuote(int age, string name) {age 21;name wowo; } void funcByAddr(int* age, string* name) {*age 21;*name wowo; }int main() {int age 10;string name yoyo; cout age , name endl;funcByValue(age, name);cout age , name endl;funcByQuote(age, name);cout age , name endl;funcByAddr(age, name);cout age , name endl;return 0; }把函数的形参声明为引用调用函数时形参将是实参的别名该方法称为引用传递。 引用的本质是指针常量传递过程中传递的是变量的地址故函数中对形参的修改会影响实参。 传值、传地址、传引用相比引用传递的优点 1传引用更简洁且避免了不必要的值拷贝 2引用传递避免了二级指针 #include iostream using namespace std; void funcByQuote(int* p) {p new int(3);cout *p endl; } void funcByAddr(int** p) {*p new int(3);cout **p endl; }int main() {int* p nullptr;funcByQuote(p);funcByAddr(p);return 0; }引用传入函数的形参用const修饰 作用 引用为const时c将创建临时变量并让引用指向临时变量。何时创建临时变量 1引用的数据对象类型不匹配c会创建正确类型的匿名变量将实参的值传递给匿名变量并让形参来引用该变量 2引用的数据对象类型匹配但不是左值 const int val 8; // 等价于 int tmp 8; const int val tmp;如果不想函数修改引用传入的实参可以在形参列表中数据类型前加const修饰 void funcByQuote(const int val1, const int val2);原因 1使用const可以避免函数中无意修改数据而造成的错误 2使用const函数能正确的生成临时变量 3使用const函数就能够处理const和非const实参否则只能接受非const实参 引用用于函数返回值 如果返回局部变量的引用本质上是野指针 可以返回函数的引用形参、类的成员、全局变量、静态变量 #includeiostream #includestring using namespace std;struct Stu {string name;int age; };// 返回函数的引用形参 ostream operator(ostream out, const Stu stu) {out stu.name : stu.age endl;return out; }int main() {Stu stu{wowo, 12};cout stu endl;return 0; }如果不希望返回引用被利用可在其前面加const #includeiostream using namespace std; int func1(int n) {return n; } const int func2(int n) {return n; }int main() {int a 1;int b func1(a);cout func1(a) , a , b endl;func1(a) 10;cout func2(a) , a , b endl;const int b2 func2(a);// func2(a) 12; // error: assignment of read-only location ‘func2(a)’cout func2(a) , a , b endl;return 0; }类 class / 结构体 struct 类/结构体中的每个变量都有自己独立的内存。 类/结构体数据对齐的问题 遵循“缺省对齐”的原则。32位CPU中char可以占用任何地址、short可以占用偶数地址、int占用4的整数倍的地址、double占用8的整数倍的地址。 类/结构体内存布局 32位CPU是以4字节为一个单位的故内存布局在默认情况下一般不是紧密排列的。内存布局“遵循最大数”原则即类中如果有double属性则占用的总内存是8的倍数。可以通过#pragma pack(n)中通过设置不同的n来表示内存排列的紧密程度n1表示内存是紧密排列的。 结构体中的全部成员清零 struct student {int age;char name[21]; }// void* memset(void* dest, int ch, size_t count)只适用于结构体成员是c的基本数据类型 student stu1; memset(stu, 0, sizeof(student)); student stu2; memcpy(stu2, stu1);复制结构体 可以用或void memcpy(void* dest, void* src)函数。 结构体指针 struct student {char name[21];int age; }student stu; student* stuPtr stu; (*stuPtr).name; stuPtr-name;结构体数组 struct student {char name[21];int age; }student stu[3];stu[0].name; (stu i)-name; (*(stu i)).name ssuu;结构体中的指针 #include iostream #include cstring using namespace std;struct PtrStruct {int a;int* ptr; };int main() {PtrStruct ptrStruct;// 会将结构体中的a0ptrnullptrmemset(ptrStruct, 0, sizeof(PtrStruct)); ptrStruct.ptr new int[20];// 如果结构体中的指针已经动态分配了内存空间再用memset清零时需要逐个字段清零ptrStruct.a 0; memset(ptrStruct.ptr, 0, 20 * sizeof(int)); return 0; }结构体中的指针指向的是动态分配的内存地址对结构体直接用memset()函数可能会造成内存泄露要逐个字段分情况的进行memset()清零。类(class)只使用构造函数进行初始化不要调用memset进行清零操作。用memset清零时会将结构中所有字节置0如果结构体中有虚函数或结构体成员中有虚函数则会将虚函数指针置0/置空后续程序调用虚函数空指针很可能导致程序崩溃 联合体 union #include iostream #include cstring using namespace std; struct widget {char brand[20];int type;union id{long id_num;char id_char[21];}id_val; };int main() {widget prize; cin prize.type;if (prize.type 1) { prize.id_val.id_num 12;cout prize.id_val.id_num endl;} else {strncpy(prize.id_val.id_char, hello, 6);cout prize.id_val.id_char endl;}return 0; }共用体是一种数据格式它能存储不同的数据类型但只能同时存储其中的一种类型即共用体只能存储int、long或double而结构体可以同时存储int、long和double。联合体中的数据共享一块内存且共同体占用内存的大小是它最大的成员占用的内存大小且要满足内存对齐原则。联合体中的值为最后被赋值的那个成员的值。 匿名共用体 struct widget {char brand[20];int type;union // 在定义时创建匿名联合体变量也可以嵌套在结构体中// 其成员位于相同地址的变量故每次只有一个成员是当前的成员{ long id_num;char id_char[20];}; };int main() {widget prize; if(prize.type 1)cin prize.id_num; elsecin prize.id_char; }枚举 enum enum不仅能够创建符号常量还能定义新的数据类型。 使用细节 // 创建枚举类型wt默认从0开始还可以任意设置枚举量的值但必须是整数 enum wt{Monday, Tuesday, Wednesday, Thursday, Saturday, Sunday};// 创建枚举变量并赋初始值 wt weekday Monday; weekday wt(1); // 此时weekday Tuesday枚举值不可以做左值。枚举变量可以赋值给非枚举变量非枚举值不可以赋值给枚举变量。 符号常量 #define 或 const const常量声明为常量的变量是只读的。 // 常量指针不能通过解引用的方法修改内存中的值但可尝试使用原始的变量修改。 const 数据类型* 变量名;// 指针常量引用指向的变量不可改变但可通过解引用修改变量在内存中的值定义时必须初始化否则没有意义。 数据类型* const 变量名;// 常指针常量常引用指向的变量不可改变也不能通过解引用修改变量在内存中的值。 const 数据类型* const 变量名; 常量表达式constexprc11引入在编译时就会求值提高了系统的性能。 c11新增的long long类型 VS中long类型占4 byteslong long类型占8 bytes。linux中long类型和long long类型都占用了8 bytes。 自动推导类型 c11中编译器在编译期时推导auto声明的变量的数据类型故不会造成程序运行效率的下降。 注意 auto声明的变量必须在定义时初始化初始化的右值可以是具体数值也可以是表达式和函数的返回值auto不能作为函数的形参类型auto不能直接声明数组auto不能定义类的非静态成员变量 auto的真正用途 代替冗长复杂的变量声明 代替函数指针类型 #include iostream using namespace std;int func(int val1, int val2) {return val1 val2; } int main() {/*数据类型的别名typedef 类型名 新的类型名;如typedef unsigned int size_t为了避免类型名太长造成代码可读性下降。*/// typedef定义函数类型typedef int(f)(int,int);f* fPtr1 func;cout fPtr1(1,2) endl;// typedef定义函数指针类型typedef int(*fPtrType)(int,int);fPtrType fPtr2 func;cout fPtr2(1, 2) endl;// 声明函数指针int(*fPtr3)(int,int);fPtr3 func; // 定义函数指针cout fPtr3(1, 2) endl;// 通过右值auto能自动推导出函数指针类型auto fPtr4 func;cout fPtr4(1, 2) endl;return 0; }用于lambda表达式 void关键字 void表示无类型主要有如下用途 函数返回值用void表示函数没有返回值 函数形参 填void表示函数不需要参数或让参数列表是空 填void*表示接受任意数据类型的指针要将void*类型转换成其他类型需要显式转换 其他类型的指针 -- void*指针不需要转换void*指针 -- 其它类型的指针需要转换。 零初始化 零初始化值int 0、指针 nullptr、bool false。三种零初始化方式int a {}、int a int()、int a{}。 类型转换 c的类型转化 隐式类型转换如double f 1.0 / 3显式类型转换(类型说明符)(表达式) 存在的问题任何类型之间都能进行转换且编译器无法判断其正确性 c的类型转换 自动/隐式类型转换 系统自动进行不需要开发人员介入。 强制类型转换 强制类型转换名type (express) static_cast最常用 静态类型转换编译的时候就会进行类型转换检查。不会产生动态类型转换的类型安全检查的开销。与c语言中的强制类型转换差不多。 用途 相关类型转换比如整型和实型转换 int i 5; double d static_castdouble(i);double d 5.0; int i static_castint(d);类中子类与父类之间的转换且只能是子类转换为父类 class A {. . . . } class B : public A {. . . . }B b; // 子类能转换为父类 A a static_castA(b);void*与其他类型的指针之间的转换 int a 10; void* ptr_int a; double* ptr_double static_castdouble*(ptr_int);void*无类型指针可以指向任何指针类型即万能指针 主要用于函数的形参中用void*即“实参的类型指针-void*指针-函数中使用的类型指针” // 其他类型指针 - void* - 其他类型指针 void func(void* ptr) {double* ptr_double static_castdouble*(ptr); }int main() {int a 10;func(a); }注意一般不能用于指针类型之间的转换比如int *、float *、double *等 int a 10; double* ptr_double static_castdouble*(a); // 会报错static_cast不支持不同类型指针之间的转换reinterpret_cast 重新解释将操作数的内容解释为另一种不同的类型可以处理无关类型的转换且编译时就会进行类型转换检查。 不检查指向的内容也不检查指针类型本身。但要求转换前后的类型所占用的内存大小一致否则会引发编译时错误。目标类型和(表达式)中必须有一个似乎指针/引用类型。不能丢掉(表达式)中的const和volitale属性。 常用与两种转换 void func(void* ptr) {long long i reinterpret_castlong long(ptr);cout i endl; }int main() {long long i 10;// 要求转换前后类型占用的字节数一致。这里long long占用8字节、指针也占用8直接func(reinterpret_castvoid*(i)); }将指针/引用转换成整型变量。将整型变量转换成指针/引用。改变指针/引用类型不需要像static_cast要借助void*。 dynamic_cast 动态转换主要用于运行时类型识别和检查。只能用于含有虚函数的类必须用在多态体系中用于类层次间的向上和向下转换向下转换时如果是非法的指针则返回NULL。主要用于父类和子类之间的转换父类指针指向子类对象通过dynamic_cast把父类指针转换为子类指针。 const_cast #include iostream #include string #include cstring using namespace std;int main() {string str1(5, );string str2 123;strncpy(const_castchar*(str1.data()), str2.c_str(), str2.size()); cout str1 ,;return 0; }只能去除指针 或者 引用的const属性。 const int a1 1; // int a2 const_castint(a1); // 报错a1不是指针或者引用const int* a2 a; int* a4 (int*)(a2); // C风格的强制转换 int* a3 const_castint*(a2); // c风格的强制转换总结 c推出的类型转换替换c风格的类型转换采用更严格的语法检查降低使用风险。一般static_cast和reinterpret_cast能够很好的取代C语言风格的类型转换。 静态变量 静态变量的存储方式和生命周期属于静态存储方式其存储空间为内存中的静态数据区该区域的数据在整个程序的运行期间不会释放所以其生命周期为整个程序运行时间段。 静态局部变量定义在函数体内的变量。 1当对静态局部变量进行初始化时只初始化一次且必须是常量或常量表达式 2局部静态变量编译阶段不分配内存只有在执行并且调用其所在的函数时才会分配内存 全局变量与静态全局变量两者的区别是作用域不同。 1非静态全局变量的作用域是整个源程序当一个源程序由多个源文件组成时非静态的全局变量在所有源文件中都是有效的 2静态全局变量只在定义该变量的源文件内有效可以增加安全性和避免不同源文件同变量名冲突问题。 静态、动态分配内存 动态内存分配 运行期间分配程序结束前必须释放内存分配的空间否则会造成内存泄露。 程序执行较慢因内存在程序执行时才进行分配一般分配的是连续的内存空间。 指向动态分配的内存空间的指针在使用完成后需要程序员释放掉否则会造成内存泄漏。 动态内存分配的注意事项 堆、栈的不同用途和区别 栈空间有限编译器自动分配速度较快。堆只要不超过实际的物理内存而且在操作系统能分配的最大内存大小内都可以分配分配速度慢通过malloc/free、new/delete来实现。 实现 C语言 通过malloc/free从堆区申请和释放内存malloc(memory allocation)动态内存分配。 void* malloc(int NumBytes) // NumBytes是要分配的字节数 // 分配成功返回指向被分配内存的指针分配失败返回NULL当不使用这段内存时要用void free(*FirstBytes)函数将这段内存释放并被系统回收需要时重新分配。 char* ptr (char*)malloc(13 * sizeof(char)); // 在堆中分配四个字节if (otr ! nullptr) {strcpy_s(ptr, 13, hello world!);cout ptr endl;// 释放内存ptr nullptr;free(ptr); }给申请的100个整型内存空间赋值 // 给申请的100个整型内存空间赋值0-100 // 分配400个字节 int* ptr (int*)malloc(100 * sizeof(int)); if (ptr ! nullptr) {// 通过指针ptr1给指向ptr的内存空间赋值int* ptr1 ptr;for (int i 0; i 100; i){*ptr1 int(i); // 等价于*(ptr1 i) i;}// 输出申请的100个整型内存空间的值if (ptr1 ! nullptr){for (int i 0; i 100; i){cout *(ptr i) ; }cout endl;}// 释放申请的内存 free(ptr); ptr nullptr; }c语言 用new/delete运算符标识符分配和释放在堆区的内存。 // new使用的一般格式 // 1. 申请一个堆区的内存 指针变量名 new 类型标识符; 指针变量名 new 类型标识符(初始值); // 2. 申请一些连续的堆区的内存 指针类型名 new 类型标识符[内存单元的个数];new动态的分配内存然后调用对应的构造函数递归调用各个成员变量的构造函数编译器自动进行。new类对象时加不加括号的差别 A* a1 new A(); // 加圆括号 A* a2 new A; // 不加圆括号如果new一个空类则两种方式并无区别。如果类中含有成员变量则带括号的初始化会将一些成员变量相关的内存清零但并非所有内存空间全部清零虚函数表指针不能清零。当类中有构造函数时带/不带圆括号完全相同。 delete调用对应的析构函数编译器自动进行然后释放内存。 注意两者void* operator new(size_t size)、void* operator new[](size_t size)内部函数体相同只是编译器推导出的size字节数不同。 nullptr c11引入的新关键字nullptr代表空指针NULL也代表空指针实际上是整型数0。 引入nullptr能够避免整数和指针之间发生混淆。NULL和nullptr实际上是不同的类型之后涉及指针时就用nullptr。 动态分配内存的布局 除了需要的内存外为了管理动态分配的内存故还需要一些额外信息频繁的动态分配会造成资源的极大浪费特别是申请小块内存时。 malloc与free的实现原理 malloc底层是brk、mmap系统调用实现的free底层是unmap系统调用实现的。 malloc小于128k的内存使用brk分配内存将数据段(.data)的最高地址指针_edata往高地址推 malloc大于128k的内存使用mmap分配内存在堆和栈之间称为文件映射区域的地方找一块空闲内存分配 这两种方式分配的都是虚拟内存没有分配物理内存。当第一次访问已分配的虚拟地址空间时会发生缺页中断操作系统负责分配物理内存并建立虚拟内存和物理内存间的映射关系。 操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时就会遍历该链表并寻找第一个空间大于所申请空间的堆结点将该结点从空闲结点链表中删除并分配给程序。 被free回收的内存是立即返归还操作系统吗 不是的被free回收的内存会首先被ptmalloc使用双链表保存起来当用户下一次申请内存的时候会尝试从这些内存中寻找合适的返回。这样就避免了频繁的系统调用占用过多的系统资源。 同时ptmalloc也会尝试对小块内存进行合并避免过多的内存碎片。 静态内存分配 编译阶段分配并在程序结束时自动归还给系统。较快因程序在编译阶段即已决定内存所需要的容量但这容易造成内存的浪费。 内存泄露的问题 一般程序中已动态分配的堆内存由于某种原因程序未能及时释放或者无法释放造成系统资源的浪费导致程序运行速度减慢甚至系统崩溃。 内存泄漏在服务器上尤为明显因服务器上的程序一旦运行不能随意中断故不断泄露内存会使系统资源被极大的占用和浪费导致出现程序运行速度减慢或者崩溃的问题。 数据的输入输出控制 使用控制字符 #include iostream cout setprecision(3) ... // 保留三位有效数字 cout fixed setprecision(3) ... // 保留小数点后三位有效数字 cout scientific setprecision(3) ... // 小数点后三位有效数字的科学计数 cout setfill(%) setw(5) num1 nnum2 endl;cout的输出顺序自左向右计算顺序自右向左 cout作为输出流先将数据从右向左读入缓冲区再从缓冲区读写到屏幕类似堆栈。 cout本质上是类ostream的一个对象。 备注 一般形式 void *malloc(int NumBytes) // NumBytes是要分配的字节数 // 分配成功返回指向被分配内存的指针分配失败返回NULL #includeiostream #includecstdio// 宏定义namespace x {} #define BEGINS(x) namespace x { #define ENDS(x) } // 命名空间SelfCout BEGINS(SelfCout)class ostream { public:// 返回值为ostream可使“运算符”连续使用ostream operator(int x);ostream operator(const char *x); };ostream ostream::operator(int x) {printf(%d, x);return *this; }ostream ostream::operator(const char *x) {printf(%s, x);return *this; } ostream cout; // cout是类ostream的一个对象ENDS(SelfCout)int main() {int n 123, m 456;std::cout n m; std::cout std::endl;SelfCout::cout n m; std::cout std::endl;return 0; }c的关键字 c的运算符 按运算性质算数运算符、自增自减、赋值运算符/、%、关系运算符、逻辑运算符、||、、位运算符右移操作比较复杂逻辑右移、算数右移左边空缺位的填充不同、左移操作直接给右边的空缺位补0、杂项运算符。 按运算对象单目运算符一个运算对象、双目运算符两个类型相同的运算对象、三目运算符条件运算符 condition ? X : Y。 其他运算符 字节数运算符sizeof 返回变量的大小指针运算符var 返回变量的地址指针运算符*var 返回变量var 结构体、类 结构变量、对象一块能够存储数据且具有某种类型的内存空间。 c中定义一个属于该结构的变量称为结构变量。c中定义一个属于该类的变量称为对象。 c中结构体和类具有相似性区别主要有两点 内部的成员变量、成员函数默认的访问权限不同结构体 – public、类 – private。继承默认的权限不同结构体 – public、类 – private。 结构体 // 使用结构体作为函数的形参 struct Student {int num;char name; } student;void func1(Student tempStu) // 结构体作为函数的形参 {tempStu.num 20;strcpy_s(tempStu.name, sizeof(tempStu.name), lisi); } // 效率低因为在实参传递给形参时发生了内存内容的拷贝操作 func1(student);void func2(Student tempStu) // 函数的形参变为结构体引用 {tempStu.num 20;strcpy_s(tempStu.name, sizeof(tempStu.name), lisi); } func2(student);void func3(Student* tempStu) // 指向结构体的指针做函数参数 {tempStu-num 20;strcpy_s(tempStu-name, sizeof(tempStu-name), lisi); } func3(student);静态对象与全局对象的构造顺序 函数/类中的静态对象 多次调用函数静态对象只会创建一次即一个函数的静态局部变量在函数被多次调用时只初始化一次。 #include iostream using namespace std;void func() {static int a 1; // 多次调用函数静态对象只会创建一次a;cout a ; }int main() {func(); func(); func(); // 2 3 4return 0; }类中的静态对象只有声明且定义后才能被调用。 全局对象的构造顺序 如果项目中有多个.cpp文件且每个源文件中都定义了不同的全局对象则这些全局对象的构造顺序是无规律的不能在构造某个全局对象时直接使用另一个全局对象无法确定该对象是否在使用前被构造 临时对象 产生临时对象的情况和解决 以传值的方式给函数传递参数 类型转换生成的临时对象 类名 obj; obj 100; // 这里产生了一个真正的临时变量后干了三件事 // 1用100创建一个该类的临时对象 // 2调用拷贝赋值运算符把这个临时对象里的各个成员赋值给obj对象 // 3调用析构函数销毁创建的临时对象// 把定义对象和给对象赋值放在同一行 // 这就为obj对象预留了空间避免了使用临时对象 类名 obj 100;隐式类型转换以保证函数调用成功 函数返回局部对象时 注意c中只会为const引用const string str产生临时对象不会为非const引用string str产生临时对象。 深、浅拷贝的问题 浅拷贝只拷贝指针地址 c默认为每个类生成的“拷贝构造函数”与“重载的赋值运算符”都是浅拷贝。优点节省空间缺点容易引发多次释放、内存泄漏的问题。 深拷贝重新分配内存拷贝指针指向的内容 缺点浪费空间优点不会导致多次释放 #include iostream #include cstring using namespace std;class Stu { public:int age;string name; char* phone; // 使用堆区开辟的内存空间 public:Stu() : age(0), name(), phone(nullptr){cout default constructor endl;}Stu(int m_age, string m_name, char* m_phone) : age(m_age), name(m_name){this-phone new char[sizeof(m_phone)];memcpy(this-phone, m_phone, sizeof(m_phone));cout with the constructor endl;}Stu(const Stu stu){this-age stu.age;this-name stu.name;/*// 此时会存在“浅拷贝”的问题如果Stu stu3(stu2)中stu2先释放stu3.phone的使用就会崩溃this-phone stu.phone; */// 深拷贝this-phone new char[sizeof(stu.phone)];memcpy(this-phone, stu.phone, sizeof(stu.phone));cout copy constructor endl;}~Stu(){delete[] this-phone;this-phone nullptr;cout destructor endl;} friend ostream operator(ostream out, const Stu stu); };ostream operator(ostream out, const Stu stu) {out stu.age , stu.name , stu.phone endl;return out; }int main() {Stu stu1;Stu* stu2 new Stu(12, wowo, (char*)121212);Stu stu3(*stu2);delete stu2; // 如果Stu类的拷贝函数中存在“浅拷贝”的问题则stu2先释放stu3.phone的使用就会崩溃cout stu3 endl;return 0; }如何兼顾两者的优点 引用计数会带来额外的内存开销 c新标准中std::move()移动语义 const int len 100;class String { public:// 普通构造函数String(const char* str NULL){if (str NULL){this-data new char[1]this-data \0;}else {int len strlen(str.data);// 字符串结束符\0占一字节this-data new char[len 1]; strcpy(this-data, str);}}// 拷贝构造函数String(const String str){int len strlen(str.data);// 字符串结束符\0占一字节this-data new char[len 1]; if (this-data ! NULL){strcpy(this-data, str);}else {exit(-1);}}// 赋值运算符String operator(const String str){if (this-data ! str){delete[] this-data;this-data new char[strlen(str.data)1];if (!this-data){strcpy(this-data, str.data);}}return *this;}// 移动构造函数String(String str){if (str.data ! NULL){// 资源的让渡this-data str.data;str.data NULL;}}// 移动赋值运算符String operator(String str){if (this-data ! NULL){delete[] this-data;// 资源的让渡this-data str.data;str.data NULL;}return *this;}virtual ~String(){if (this-data ! NULL){delete[] this-data;this-data nullptr;}} public:char* data; }int main() {String str1(hello);String str2(std::move(str1));String str3 std::move(str2); }左值/右值、左值引用/右值引用、万能引用、move、移动语义、完美转发 templateclass T void swap(T a, T b) { // 以下三个语句在执行时都会发生拷贝动作const T tmp a;a b;b tmp; }templateclass T void swap(T a, T b) { // perfect swapT tmp std::move(a);a std::move(b);b std::move(tmp); }左值、右值 左、右值区别 左值代表一个地址右值代表一个值c中一个表达式只能是左值或者右值之一左值是可以被引用的数据可以通过地址访问如变量、数组元素、结构体成员、引用和解引用的指针左值可以同时具有左值和右值属性如 i i 1;右值非左值包括字面常量用双引用包含的字符串除外它是有地址的和包含多项的表达式 class A;int i 3; // i是左值3是右值 i i 3; // 左边的i是左值右边的i3是右值A func() {return a; } A a1 func1(); // a1是左值func1()返回的返回值类型是A故为右值A func2(A a) {return a; } A a2 func2(); // a2是左值func2()返回的返回值类型是A故为左值/* 总的来说 1. 右值无法取地址而左值可以 2. 左值有名字而右值没有 3. 表达式结束后左值仍然存在右值就不再存在 */c11中扩展了右值的概念分为纯右值、将亡值 纯右值 非引用返回的临时变量运算表达式产生的结果字面常量c语言风格的字符串是有地址的 将亡值与右值引用相关的表达式 将要被移动的对象T函数的返回值std::move()函数的返回值转换成T类型的转换函数的返回值 使用左值的运算符 赋值运算符整个赋值语句的结果仍然是左值取址符string、vector容器 通过判断运算符能够对数字进行直接操作进而可以判断是否是左值不能直接对数字进行操作则该运算符要用左值下标[ ]就是一个左值迭代器iter也是左值即vectorint::iterator iter 不是左值就是右值 临时变量被当作右值。 引用类型c98中均为左值引用c11开始出现右值引用 左值引用lvalue reference绑定到左值即给左值起别名 int val1 3; // 左值引用 int val2 val1;没有空引用的说法左值引用初始化时必须绑定到左值 引用左值时必须绑定到左值上不能绑定到右值数字上 常量左值引用是一个万能的引用可以绑定非常/常量左值、右值缺点只读不能修改 int b 10; const int c 10;const int rb b; // 常量左值引用绑定非常量左值 const int rc c; // 常量左值引用绑定常量左值const int rval 10; // 常量左值引用绑定右值右值引用rvalue reference绑定到右值即给右值起别名 // 右值引用 const int val 4; // 系统利用的是临时变量temp // int tempVal 4; // const int val tempVal;int val2 val 3; // val3是右值 /* 右值有了名字就变成了左值 */系统希望用右值引用来绑定一些即将被销毁或者临时的对象上。 class A;// 函数的返回值是右值临时变量 A getTmp() {return A(); }int main() {// 右值引用函数返回的临时变量A a getTmp();// 这样在构造a的过程中只调用了默认/有参构造函数而没有调用拷贝构造函数效率更高 }右值引用的目的c11引入右值引用代表一种新的数据类型来提高系统效率把拷贝对象变成移动对象。常被用于移动语义中即移动构造函数和移动赋值运算符的形参列表中。 总结 左值引用使用T只能绑定左值右值引用使用T只能绑定右值已命名的右值引用是左值常量左值const T既可以绑定左值又可以绑定右值 move函数c11标准库中的新函数 作用将一个左值强制转换为右值。 int val1 3; int val2 std::move(val1); // val2相当于val1的引用string str1 I love China!; string str2 std::move(str1); // 调用string中的移动赋值运算符将str1中的内容移动到str2中去了本质将对象的状态/所有权从一个对象转移到另一个对象只是转移没有内存的搬迁/内存拷贝所以可以提高利用效率、改善性能。 #include iostream #include vector #include string using namespace std; int main() {string str Hello;vectorstring vctor;// 调用常规的拷贝构造函数新建字符数组拷贝数据vctor.push_back(str);cout str endl;// 调用移动构造函数掏空str掏空后尽量不要再使用str该过程中没有发生内存的拷贝和释放只是所有权发生了变化vctor.push_back(std::move(str));cout str \t v[0] \t v[1] endl; }万能引用T存在的前提为模板参数类型、const T 如果模板类模板、函数模板中参数为T那么既可以接受左值引用又可以接受右值引用。 #include iostream using namespace std; templatetypename T void funcLRVal_(T val) // T作为参数类型既可以接受左值又可以接受右值 {... }int main() {int a 10; funcLRVal(a);funcLRVal(10); return 0; } 加const修饰后即const T就只能接受右值引用。 #include iostream using namespace std; templatetypename T void test(const T val) {cout void test(const T val) endl; }int main() {int a 10; test(10);// test(a); // 报错return 0; } int、vectorT“具体类型”或“非T”则均不是万能引用。 类模板的成员函数在类实例化后成员函数的参数类型已确定即并不再是模板参数故不会是万能引用除非成员函数是函数模板且参数类型为T。 const T既能接受左值引用又能接受右值引用。 #include iostream using namespace std; templatetypename T void test(const T val) {cout void test(const T val) endl; }int main() {int a 10; test(10);test(a);return 0; }缺点函数体内不能对参数进行修改。 移动语义 #include iostream #include cstring using namespace std;class A { public:int* m_data nullptr; // 指向堆区资源的指针类内初始化A() default; // 启用默认的构造函数void alloc(){m_data new int; // 分配堆区内存memset(m_data, 0, sizeof(int)); // 将分配的内存初始化为0}A(const A a) // 拷贝构造函数{cout A(cosnt A a) endl;if (m_data nullptr) { alloc(); }memcpy(m_data, a.m_data, sizeof(int));}A operator(const A a) // 拷贝赋值函数{cout A operator(const A a) endl;if (this a) { return *this; } // 避免自我赋值if (m_data nullptr) { alloc(); }memcpy(m_data, a.m_data, sizeof(int));return *this;}~A(){delete m_data;cout ~A() endl;}A(A a) // 移动构造函数形参不能用const修饰因最后要将a.m_data置空{cout A(const A a) endl;if (m_data ! nullptr) // 如果已分配内存则先释放掉{ delete m_data; }m_data a.m_data; // 将源对象中的指针指向的内存地址赋值给新对象中的指针a.m_data nullptr; // 将源对象中的指针置空} A operator(A a) {cout A A(A a) endl;if (this a) // 避免“自我赋值”{ return *this;}if (m_data ! nullptr) // 如果已分配内存则先释放掉{ delete m_data; }m_data a.m_data; // 将源对象中的指针指向的内存地址赋值给新对象中的指针a.m_data nullptr; // 将源对象中的指针置空return *this; }};int main() {A a1;a1.alloc();*(a1.m_data) 3;cout *(a1.m_data) endl;A a2 a1; // 调用拷贝构造函数cout *(a2.m_data) endl;A a3;a3 a2; // 调用拷贝赋值函数cout *(a3.m_data) endl;cout .............. endl;A a4(std::move(a1)); // 调用移动构造函数A a5 std::move(a2); // 调用移动构造函数A a6; a6 std::move(a3); // 调用移动赋值函数return 0; } 如果一个函数中有堆区资源则需要编写拷贝构造函数和赋值函数实现深拷贝。 移动语义通过直接使用源对象拥有的资源可以节省资源申请和释放的时间。 c中所有容器都实现了移动语义避免对含有堆区资源的对象发生不必要的拷贝。 移动语义对于拥有资源如堆区内存、文件句柄的对象有效如果是基本类型使用移动语义没有意义。 实现移动语义要增加两个成员函数移动构造函数类名(类名 源对象)和移动赋值函数类名 operator(类名 源对象)。 注意形参不能用const修饰因函数体内要源对象指向的内存进行置空。 c提供std::move()方法将左值转义为右值从而能方便使用移动语义。 左值对象被转移资源后不会立刻析构只能在离开自己作用域的时候才能析构如果继续使用左值中的资源可能会发生意想不到的错误。 完美转发 函数模板中可以将参数 “完美转发” 给其内部调用的其它函数。 “完美” 指的是①准确地转发参数的值②保证被转发参数的左、右值属性不变 完美转发与否影响参数在传递过程中采用拷贝语义还是移动语义。 为实现完美转发c11提供的方案 #include iostream #include cstring using namespace std;void func(int val) {cout params are right value endl; }void func(int val) {cout params are left value endl; }templatetypename T void funcLRVal(T val) // T作为参数类型既可以接受左值又可以接受右值 {func(val); }// 完美转发 templatetypename T void funcLVal(T val) {func(val); } templatetypename T void funcRVal(T val) {func(std::move(val)); } templatetypename T void funcLRVal_(T val) // T作为参数类型既可以接受左值又可以接受右值 {func(std::forwardT(val)); // 将左值转发后仍是左值引用右值转发后仍是右值引用 }int main() {int a 10;// 在模板函数中模板函数的参数转发给func()函数后都变成了左值funcLRVal(10);funcLRVal(a);cout endl;/* 实现完美转发的两种方案 */// 1、通过两个模板函数分别实现右值和左值的转发funcLVal(a);funcRVal(10);// 2、采用forwardT转换funcLRVal_(a); funcLRVal_(10); return 0; } 1如果模板类模板、函数模板参数写为万能引用T那么既可以接受左值引用又可以接受右值引用。 #include iostream using namespace std; templatetypename T void funcLRVal_(T val) // T作为参数类型既可以接受左值又可以接受右值 {... }int main() {int a 10; funcLRVal(a);funcLRVal(10); return 0; } 2提供了模板函数std::forwardT(参数)用于转发参数 templatetypename T void funcLRVal_(T val) // T作为参数类型既可以接受左值又可以接受右值 {func(std::forwardT(val)); // 将左值转发后仍是左值引用右值转发后仍是右值引用 }如果参数是一个右值转发后仍是右值引用如果参数是一个左值转发后仍是左值引用 forwardT通过T来决定来推断并转发的。 #include iostream using namespace std;void Print(int val) {cout Print(int val) endl; }void Print(int val) {cout Print(int val) endl; }template typename T void func(T tmp) {Print(std::forwardT(tmp)); }int main() {func(10); // T int、tmp int// 等价于Print(std::forwardint(10));int i 10;func(i); // T int、tmp int // 等价于Print(std::forwardint(i));return 0; }普通函数实现完美转发。 #include iostream using namespace std;void func(int val) { cout void func(int val) endl; } void func(int val) { cout void func(int val) endl; }void funcLR(auto tmpVal) {func(std::forwarddecltype(tmpVal)(tmpVal)); }int main() {int i 10;funcLR(i);funcLR(std::move(i));return 0; }构造函数模板中使用完美转发以及对拷贝/移动赋值的影响。 #include iostream #include string using namespace std;class Human { public:/* Human的构造函数 *//*// 初始化列表中会调用string(const string str)的拷贝构造函数Human(const string name) : _name(name) { cout Human(const string name) endl;}// 右值传入后name会变成左值std::move()只会将左值转换为右值// 初始化列表中会调用string(string str)的移动构造函数Human(string name) : _name(std::move(name)) {cout Human(string name) endl;}*/// 构造函数的完美转发// 法一//Human(auto name) : _name(std::forwarddecltype(name)(name))//{// cout Human(auto name) endl;//}// 法二templatetypename THuman(T name) : _name(std::forwardT(name)){cout templatetypename T Human(T name) endl;}/* Human的拷贝构造函数 */Human(const Human human) : _name(human._name){cout Human(const Human human) endl; }/* Human的移动构造函数 */Human(Human human) : _name(std::move(human._name)){cout Human(Human human) endl; }private:string _name; };int main() {/* 构造函数 */Human human1(string(hi));string name hi;Human human2(name);/* 拷贝构造函数 *///Error受到构造函数中的函数模板的影响不能正常地调用到拷贝构造函数//Human human3(human2);//解决方案通过std::enable_if解决const Human human4(string(hi));// 因human4 : const Human类型故能正常地调用到拷贝构造函数Human human5(human4);/* 移动构造函数 */// 不受到构造函数中的函数模板的影响能正常地调用到移动构造函数Human human6(string(hi));Human human7(std::move(human6));return 0; } // std::move()实现的移动构造不受影响可正常调用。 // 只有const Human类型才能正常地调用到拷贝构造函数不加const则会因构造函数模板的存在使程序报错。可变参数模板中使用完美转发。 #include iostream using namespace std;int func(int val1, int val2) {val2;return val1 val2; }template typename F, typename... T //auto Func(F f, T... t) - decltype(f(std::forwardT(t)...)) // 存在丢失引用的可能 decltype(auto) Func(F f, T... t) // 解决上面提到的“引用丢失”的问题 {return f(std::forwardT(t)...); }int main() {int j 10;cout Func(func, 20, j) endl;cout j endl;return 0; }1支持任意数量、参数类型的完美转发 2可变参数模板需要返回值时可使用decltype(auto)作为返回值类型 函数新特性、函数重载、inline内联函数、函数中const的使用、递归函数 函数新特性 函数定义中形参如果在函数体中没有使用到则可以不给形参变量名字只给其类型。 函数声明中可以只有形参类型没有形参名。 函数定义前置返回类型、后置返回类型。 // 前置返回类型 返回类型 函数名(形参) {. . . . }// 后置返回类型 auto 函数名(形参) - 返回类型 {. . . . }函数的使用细节 函数调用时visual studio会从参数列表右边开始读变量的值故函数定义时形参有默认值必须放在形参列表最后。 函数传参时如f(int x)、f(int x)、f(int* x)、f(int x[])、f(int x)传值、传地址、传引用的原则 不需要在函数中修改实参 1如果实参很小比如内置数据类型、小型结构体则可按值传递 2如果实参是数组则使用 const指针没有为数组建引用的说法 3如果实参是较大的结构体则使用 “const指针 或 const引用” 4如果参数是类则使用 const引用传递类的标准方式就是 const引用 需要在函数中修改实参 1如果实参是内置数据类型则使用指针即func(a)的调用表示要在函数中修改a的值 2如果实参是数组只能用指针 3如果实参是结构体/类则使用指针/引用 函数返回指针和引用 int* func() {int tempVal 4;return tempVal; }int func() {int tempVal 15;return tempVal; }1c中更习惯引用类型的形参来取代指针类型的形参防止值拷贝引起的效率降低。 2c中允许函数同名但形参列表的参数类型或数量应该有明显的区别即函数重载函数名字相同、但参数个数/参数类型不同。 函数在反汇编后每次调用函数都需要进行入栈和出栈的操作故效率较低处理传参/返回值/栈帧的产生和销毁会带来一定的开销 如果函数体较小为了避免频繁的入栈和出栈可以将调用函数 -- 直接嵌入一段代码从而节省计算开销。 1c语言采用宏定义一个函数define Multi(x) (x)*(x-1)。由于x可能是一个表达式故需要加()避免出现错误。 2c采用inline/constexpr关键字修饰函数 // 1、可以使用内联函数函数定义前加关键字inline“编译阶段”直接将代码内嵌但编译器只是作为参考 // 特点 // 1如果函数是内联函数则在编译时编译器会把该函数的代码副本放置在每个调用该函数的地方即采用空间换时间的方式。 // 2体积小频繁调用的函数可通过引入内联函数inline提高程序性能。 inline 前置返回类型 函数名(形参) {. . . . } // 注意内联函数的定义要放在头文件这样在用到该内联函数的.cpp文件时都能够通过#include头文件找到这个内联函数的函数本体并尝试将该函数的调用改为函数本体调用。 // 优缺点存在代码膨胀的问题故内联函数体必须小循环、递归、分支尽量不要出现在函数体中。// 2、c11引入的关键字constexpr该关键字修饰的函数可以看作更严格的内联函数保证函数或对象的构造函数是编译时常量。 constexpr int get_five() {return 5;} int some_value[get_five() 7]; // Create an array of 12 integers. Valid C11 /*c11constexpr函数必须满足下述限制1函数返回值不能是void类型2函数体不能声明变量或定义新的类型3函数体只能包含编译期语句声明、null语句或者一段return语句不能是运行期语句5在形参实参结合后return语句中的表达式为常量表达式在编译时若能求出其值则会把函数调用替换成结果值故相比宏来说没有额外的开销。所有被声明为constexpr的非静态成员函数也隐含声明为const即函数不能修改*this的值即this是常量指针。c14放松了这些限制声明为constexpr的函数可以含有以下内容1任何声明除了static/thread_local变量、没有初始化的变量声明2条件分支语句if和switch3所有的循环语句包括基于范围的for循环4表达式可以改变一个对象的值只需该对象的生命期在声明为constexpr的函数内部开始。包括对有constexpr声明的任何非const非静态成员函数的调用。 */#includeiostream using namespace std;// C98/03 template int N struct Factorial_Cpp03 {const static int value N * Factorial_Cpp03N - 1::value; }; // 递归的基准点 template struct Factorial_Cpp030 {const static int value 1; };// C11 constexpr int factorial_Cpp11(int n) {return n 0 ? 1 : n * factorial_Cpp11(n - 1); }// C14 constexpr int factorial_Cpp14(int n) {int result 1;for (int i 1; i n; i){result * i;}return result; }int main() {static_assert(Factorial_Cpp033::value 6, error);cout Factorial_Cpp033::value endl;static_assert(factorial_Cpp11(3) 6, error);cout factorial_Cpp11(3) endl;static_assert(factorial_Cpp14(3) 6, error);cout factorial_Cpp14(3) endl;return 0; }/*const 和 constexpr 变量间的主要区别1const 变量的初始化可以延迟到运行时而 constexpr 变量必须在编译时进行初始化2所有 constexpr 变量均为常量因此必须使用常量表达式初始化。 */函数重载 函数重载是指设计一系列同名不同参的函数让他们完成相同/相似的工作。 实际中可以重载功能相同但参数类型不同的函数但不要重载功能不同的函数会降低代码的可读性。 c允许同名函数但条件是形参个数、数据类型、排列顺序要不同但const、返回值不作为函数重载的特征。 注意 1重载函数时如果数据类型不匹配c会尝试进行类型转换并与形参进行匹配若转换后有多个函数能匹配上则会报错 2引用可作为函数重载的条件 void func(string str, int i); void func(string str, int i);// 调用void func(string str, int i); func(a, 10);// 调用void func(string str, int i); func(wowo, 10); 3c名称修饰编译时会对每个函数名进行加密替换成不同名的函数 const用法 函数形参中带 const c更习惯引用类型的形参来取代指针类型形参防止值拷贝引起的效率降低但这可能导致修改形参值使得实参值也被无意修改形参中加入 const 可避免无意中对形参的修改导致实参被更改的问题。 struct Student {int num;char name; } void func(const Student tempStu) {... }Student student; func(student);加入const可以使实参类型更灵活既可以接受普通的数据类型也可以接受常量的数据类型包括常数。 // 使用结构体作为函数的形参 struct Student {int num;char name; } void func(const Student tempStu) {tempStu.num 20;strcpy_s(tempStu.name, sizeof(tempStu.name), lisi); }Student stu1; func(stu1); // 接受普通的数据类型 const student stu2 stu1; func(stu2); // 接受常量的数据类型包括常数常量指针和指针常量的区别 const char* ptr 等价于 char const *ptrptr指向的东西不能通过ptr修改 char* const ptrptr一旦指向一个东西之后就不能再指向其他东西但可以修改ptr指向的目标内容 递归函数 递归是一种重要的编程思想可以通过数学归纳法严格证明。 递归设计的基本准则 基准情况无须递归就能解出不断推进每一次递归调用都必须使求解状况朝接近基准情形的方向推进设计准则假设所有的递归调用都能运行合成效益法则求解一个问题的同一个实例切勿在不同的递归调用中做重复性的工作 缺点导致时间需要大量重复的运算和空间需要开辟大量的栈空间的浪费 递归的优化 以求取斐波那契数列为例提出不同的优化策略。 尾递归所有递归形式的调用都出现在函数的末尾 int f(int n, int ret0, int ret1) {if (n 0){return 0}if (n 1){return 1;}return f(n-1, ret1, ret0 ret1) ; }使用循环替代 int f(int n) {int n0 0; int n1 1;for (int i 2; i n; i){temp n0;n0 n1;n1 temp n0;}return n1; }使用动态规划即采用空间换时间的策略 int recursion_space[1000];int f(int n) {recursion[0] 0;recursion[1] 1;for (int i 2; i n; i){if (recursion_space 0){recursion_space[i] recursion_space[ i - 2] recursion_space[i - 1];}}return recursion_space[n - 1]; }c中的I/O流、I/O缓存区 I/O流 I/O缓存区 标准的I/O提供的三种类型的缓存模式 按块缓存如文件系统按行缓存\n不缓存 #includeiostream using namespace std;// cin、cout采用的是按行缓存的方式 int main() {int a;int count 0; while (cin a) {cout a endl;count;if (count 5){break;}}cin .ignore(numeric_limitsstd::streamsize::max(), \n); // 会删除掉缓冲区中多余的脏数据char ch;cin ch;cout ch endl; }文件操作 输入流的起点和输出流的终点都可以是磁盘文件是以块缓存进行读取的 数据的持久化方式文件和数据库 c将每个文件都看做是一个有序的字节序列每个文件都以文件结束标志结束 文件缓冲区又称文件缓存是系统预留的内存空间由操作系统管理 因磁盘的读写要比内存慢的多通过缓冲区可以极大降低磁盘的I/O次数从而提高磁盘存取的速度 根据输出和输入流分为输出缓冲区和输入缓冲区且不同的流的缓冲区是相互独立的 c中每打开一个文件系统就会为它分配缓冲区程序员只关心输出缓冲区即可。 缺省模式下输出缓冲区的数据满了系统才将数据写入磁盘极大的降低了磁盘的I/O次数效率更高但容易导致数据没有及时的写入磁盘掉电可能遗失数据。 输出缓冲区的操作 flush() // 刷新缓冲区将缓冲区中的内容写入磁盘文件中endl // 换行然后刷新缓冲区\n的功能只有换行unitbuf // 设置fout输出流在每次操作后自动刷新缓冲区 nounitbuf // 设置fout输出流让fout回到缺省模式下的缓冲方式 fout unitbuf; fout nounitbuf;流的状态eofbit、badbit、failbit。取值1表示设置、0表示清除。 eofbit // 当输入流操作到达文件末尾时将设置eofbit eof() // 用于检查流是否设置了eofbit fin buffer; if (fin.eof() true) {break; } cout buffer endl;badbit // 无法诊断的失败破坏流时将设置badbit一般是系统错误如存储空间不足 bad() // 用于检查流是否设置了badbitfailbit // 当输入流操作未能读取预期的字符时将设置failbit fail() // 用于检查流是否设置了failbit当三个流的状态都是0时表示一切顺利good()成员函数返回true否则返回false。 按照文件中数据的组织形式可分为 文本文件存放的是字符串以行的方式组织数据文件中的信息形式为ASCII码文件每个字符占一个字节方便阅读解码但占用的空间比较多。二进制文件存放的不一定是字符串以数据类型组织的数据内容要作为一个整体来考虑单个字节没有意义文件中信息的形式与其在内存中的形式相同由0、1组成组织数据的格式与文件用途有关但不方便阅读解码。 为节省存储空间还可采用压缩计数为保证数据安全也可采用加密技术。 文件的随机存取 文件位置指针对文件进行读/写操作时文件的位置指针指向当前文件读/写的位置 获取文件的位置指针ofstream类的成员函数是tellp()、ifstream类的成员函数是tellg()。 移动文件位置指针ofstream类的成员函数是seekp()、ifstream类的成员函数是seekg()。 std::istream seekg(std::streampos _pos); std::istream seekp(std::streampos _pos); seekp(128); seekg(128); // 文件指针移动到128字节 seekp(std::begin); seekp(std::end); // 文件指针移动到开始或结尾 seekg(std::begin)、seekg(std::end)std::istream seekg(std::streamoff _off, std::ios::seekdir _Way); std::istream seekp(std::streamoff _off, std::ios::seekdir _Way);// ios中定义的枚举类型 enum seek_dir {beg, cur, end}; seekg(30, ios::beg); // 从文件开始位置往后移动30字节 seekg(-30, ios::cur); // 从当前位置往前移动30字节seekg(30, ios::cur)从当前位置往后移动30字节 seekg(-30, ios::end); // 从文件结束位置往前移动30字节对文件进行随机存储如果文件中该处有内容则会被覆盖掉原有的内容。 文件操作的步骤 1、打开文件用于读和写open文件的打开方式// 默认是以ASCII码的形式打开ios::in打开文件进行读操作ifstream默认模式ios::out打开文件进行写操作ofstream默认模式ios::trunc如果文件存在清除原文件的内容ios::app打开文件并在追加内容ios::ate打开一个已有输入或输出文件并查找到文件尾ios::nocreate如果文件不存在则打开操作失败// 以二进制的形式打开ios::binary以二进制方式打开 2、is_open() // ifstream、ofstream是否为空检查打开是否成功 3、读或者写read、write 4、检查是否读完EOFend of file 5、close() // 关闭文件#include iostream using namespace std;int main() {string filename ./test.txt;fstream fin(filename, ios::in);if (!fout) // fout.is_open(){cout open the file is failure endl;}string buffer; // 按行读文件要保证缓冲区足够大// 写法一while (getline(fin, buffer)){cout buffer endl;}// 写法二while (fin buffer)){cout buffer endl;}fin.close();return 0; }const bufferLen 1024; bool copyBinaryFile(const string src, const string dst) {ifstream in(src, ios::in | ios::binary);ofstream out(dst, ios::out | ios::binary | ios::trunc);if (!in || ! out){return false;}// 从源文件读数据到缓冲区从缓冲区写入文件中char temp[bufferLen];while(!in.eof()){in.read(temp, bufferLen);streamsize count in.gcount(); // 从源文件读取到缓冲区的个数out.write(temp, count); }in.close();out.close();return true; }读写二进制文件 二进制文件以数据块的形式组织数据把内存中的数据直接写入文件 二进制的文件格式多种多样由业务需求而定 MP3、MP4、bmp、jpg、png自定义的二进制文件格式只有程序员自己可知即自定义的数据结构 写二进制文件 #includeiostream #include fstream using namespace std;int main() {// 自定义后缀.dat其中存放的是不同的Girl类对象fstream fout(./test.dat, ios::out | ios::binary);if (!fout.is_open()){cout open ths file is failure endl;}struct Girl{char name[31];int age;double weight;} girl;girl {lili, 12, 130.5};fout.write((const char*)girl, sizeof(Girl));fout.close();return 0; }读二进制文件 #includeiostream #include fstream using namespace std;int main() {// 自定义后缀.dat其中存放的是不同的Girl类对象fstream fin(./test.dat, ios::in | ios::binary);if (!fin.is_open()){cout open ths file is failure endl;}struct Girl{char name[31];int age;double weight;} girl; // 二进制文件以数据块的形式组织数据while (fin.read((const char*)girl, sizeof(Girl))){cout girl.name , girl.age , girl.weight endl;}fin.close();return 0; }操作文本文件和二进制文件的更多细节 1windows平台下文本文件的换行标志是\r\n以文本方式打开文件写数据时系统会将\n转换成\r\n读数据时系统会将\r\n转换成\n以二进制方式打开文件系统不会做任何转换 2linux平台下文本文件的换行标志是\n以文本和二进制方式打开文件系统不会做任何转换 3读取文件时 以文本方式读取文件的时候遇到换行符停止读入的内容没有换行符以二进制方式读取文件时遇到换行符不会停止读入的内容中包含换行符换行符被认为是数据 实际开发中从兼容性和语义的角度考虑 1以文本模式打开文本文件用行的方法操作它 2以二进制模式打开二进制文件用数据块的方法操作它 3不要以二进制模式打开文本文件也不要用行的方法操作二进制文件可能破坏二进制数据文件的格式二进制的某个字节的取值可能是换行符但也可能是整数中的某个字节 std::move()和std::ref的对比 std::ref()的作用。 std::move()c11引入的用于将左值转换为右值引用故而可使编译器选择移动语义而非拷贝语义从而优化性能。其允许在不复制对象的情况下将资源从一个对象转移到另一个对象上 对象之间传递大型数据结构临时对象的资源转移到持久化对象上 注意一旦std::move()进行资源的转移这样源对象就不能再使用了。 std::ref()c11引入的用于创建引用包装器。当需要向函数传递引用时尤其是使用标准库时如std::bind()、std::thread()等避免了这些函数默认对参数的复制。 总的来说 std::move()用于优化性能通过将资源的从一个对象转移到另一个对象上而不是复制资源。std::ref()用来创建引用包装器以便在需要传递引用而不是复制对象时使用。
http://www.hkea.cn/news/14487256/

相关文章:

  • 成都网站建设赢展合肥建网站要多少钱
  • 怎样做网站的ico图片模板网站修改教程视频
  • 别人能打开的网站我打不开三星商城官网首页
  • 做一个平台费用是多少如何优化网站速度
  • 网站 自定义表单wordpress后台是什么样的
  • 鲜花网站模板下载个人性质网站名称
  • 建设银行官方网站云服务中心桂林漓江风景区
  • 响应式网站一般做几个设计稿技术好的手机网站建设
  • 宁波自助建站模板重庆所有做网站的公司有哪些
  • 怎么给网站做百度坐标定位双重预防机制信息化平台
  • 百度网站怎么建设全网微商软件激活码货源
  • 机械网站优化网站建设思路
  • 网站制作公司多少钱wordpress构建
  • 网站的版式设计有哪些网站后台的建设
  • 做时尚网站的目的郑州做网站的大公司有哪些
  • 哪里建个人网站好推荐黄石网站建设
  • 网站风格设计原则国外怎么做推广网站
  • 怎么样开始做网站wordpress rss 修改
  • 网站安全建设架构室内设计联盟app官网
  • 揭阳网站制作建设专业团队朋友圈文案
  • 网站做404好处在线医疗 网站建设
  • 网站营销站点有你想当雄网站建设
  • 南昌做网站的流程电子产品外贸交易平台
  • 网站怎么做seo优化啊网站网络设计是怎么做的
  • wordpress如何做网站企业整站优化
  • 郑青松找谁做的网站重庆最近的新闻大事10条
  • 开发网站网页归档甜品制作网站
  • 网站打不开的原因珠海手机网站
  • 企业做网站需要注意事项黄骅港属于哪个省哪个市
  • 专业做网站公司哪家技术好wordpress自带评论表情