C++ Primer 中文版 (第5版)
电子工业出版社
C++ 11
文章目录
第一章 开始
-
标准库iostream定义了4个IO对象:cin、cout、cerr(输出警告和错误)、clog(输出程序运行时的一般性信息,存入日志文件中);
-
std::cerr << "Error!" << std::endl;
-
std::cout << "Enter two numbers: " << std::endl;
输出运算符 <<左侧必须是一个iostream对象,右侧的运算对象是要打印的值,运算符将给定的值写到给定的iostream对象中,计算结果就是其左侧运算对象,返回左侧的运算对象,第一个运算符的结果成为第二个运算符的左侧运算对象;运算符endl被称为操纵符,写入endl的效果是结束当前行,并将设备关联的缓冲区中的内容刷到设备中; -
调试时,应该保证“一直”刷新流,否则如果程序崩溃,输出可能还在缓冲区,程序崩溃位置的推断可能是错的;
-
读取输入数量不确定的输入数据,循环条件检测的是std::cin,检测流的状态。如果流是有效的,则检测成功,当遇到文件结束符(Windows系统中是ctrl + Z,然后按enter),或无效的输入(读入的值不是一个整数),istream对象的状态会变为无效:
int v = 0;
while (std::cin >> v){
...
}
- 成员函数有时也被称为方法;
- python在程序运行时检查数据类型,C++是一种静态数据类型,类型检查发生在编译时;因此,编译器必须知道程序中每个变量对应的数据类型;
第二章 变量和基本类型
基本内置类型
- 基本内置类型:算术类型(字符、整数、布尔值、浮点数)和空类型(void,不对应具体的值);
-
计算机可寻址的最小内存快称为字节byte,由8 比特bit构成;
-
存储的基本单元称为字word,通常由几个字节组成,由32或64bit构成;
-
除bool型和扩展的字符型外,其他整型可以划分为有符号和无符号两种;
-
无符号的仅能表示大于等于零的值;
-
与其他整型不同,字符型被分为三种:char、signed char、unsigned char,但字符的表现形式只有两种:带符号和无符号的;
-
char会表现为带符号或无符号的,具体由编译器决定,不建议在算术表达式中使用char(可以指定signed char 或unsigned char);
-
选择数据类型的经验准则:
-
当明确知道数值不可能为负数时,选用无符号类型;
-
算术表达式中,不要使用char 或 bool;
-
浮点数选用double,float精度不够,但两者的计算代价差不多;
-
类型转换:
-
当给无符号类型赋值一个超出表示范围的值时,结果是对无符号类型表示数值总数取模后的余数;
-
当给带符号类型赋值一个超出表示范围的值时,结果是未定义的,程序可能继续工作、崩溃、生成垃圾数据;
-
如果表达式中即有带符号的,也有无符号的,带符号数会自动转换为无符号数;
-
字面值常量:
- 默认情况下,十进制字面值是带符号的,八进制和十六进制字面值既可能带符号也可能是无符号的;
- 十进制字面值类型是int、long、long long中能匹配的空间最小的那个,如果字面值连与之关联的最大的数据类型都放不下,将产生错误;
变量
- int型数据初始化的几种方式:
-
定义于任何函数体之外的变量,默认初始化为0;
-
定义于函数体内部的内置类型的变量将不被初始化;
-
变量声明与定义:
-
为了允许把程序拆分成多个逻辑部分来编写,C++支持将程序分割为若干个文件,每个文件单独编译;
-
声明:使得名字为程序所知,一个文件如果想使用别处定义的名字,则必须包含对名字的声明;
-
定义:创建与名字关联的实体,申请存储空间,并可能为变量赋初始值;
-
任何包含显式初始化的声明,将成为定义,给extern标记的变量赋初始值,那就变成定义了;
-
在函数体内部,如果试图初始化extern标记的变量,将引发错误;
-
变量只能被定义一次(仅在一个文件中被定义),但是可以被多次声明;
extern int i; // 声明i,不是定义i
int j; // 声明并定义j
extern int k = 1; // 定义
-
标识符:
-
由字母、数字、下划线构成,必须字母或下划线开头;
-
长度没有限制,但是对大小写敏感;
-
C++为标准库保留了一些名字,自定义的标识符不能连续出现两个下划线,也不能以下划线紧连大写字母开头;
-
定义在函数体外的标识符不能以下划线开头;
-
变量名一般用小写字母,如index;
-
类名一般以大写字母开头;
-
作用域:
-
大多数作用域以花括号分隔;
-
名字的有效区域从名字的声明开始,以声明语句所在的作用域末端结束;
-
作用域中一旦声明了某个名字,内层作用域都能访问该名字,同时允许内层作用域中重新定义外层作用域已有的名字;
- 输出#3中,使用::来覆盖默认的作用域规则,因为全局作用域没有名字,所以当作用域操作符左侧为空时,获取全局作用域名字对应的变量;
复合类型:引用和指针
- 引用(reference):左值引用、右值引用
- 引用:为对象起了另外一个名字,定义引用时,程序把引用和初始值绑定在一起,而不是将初始值拷贝给引用;因为无法让引用绑定另一个对象,所以引用必须被初始化;
- 引用不是对象,只是为已经存在的对象起另一个名字;
- 因为引用本身不是一个对象,所以不能定义引用的引用;
- 引用不是对象,没有实际地址,所以不能定义指向引用的指针;
-
指针与引用的区别:
- 指针本身是一个对象,允许对指针赋值和拷贝,在指针的生命周期内可以先后指向几个不同的对象;
- 指针无须在定义时赋初始值,但会指向一个不确定的值;
int ival = 42;
int *p = &ival; // p存放着ival地址
cout << *p; // 得到指针p所指的对象
*p = 0; // 经由p为ival赋值
- NULL为预处理变量,预处理器是运行于编译之前的一段程序,当遇到预处理变量时,预处理器会自动将它替换为实际值;
- 现在的C++最好使用nullptr,尽量避免使用NULL;
pi = &ival; // pi指向了ival
*pi = 0; // ival的值被改变,指针pi并没有变
-
两个类型相同的指针,可以用==或!=比较,如果指针存放的地址相同,则他们相等;
-
如果一个指针指向某对象,另一个指针指向另外对象的下一个地址,可能两个指针值相等;
-
void*指针:
-
是一种特殊的指针类型,可以存放任意对象的地址,但存放的是什么类型的对象并不了解;
-
不能直接操作void*指针所指的对象,因为不知道这个对象是什么类型;
-
指向指针的指针
-
指向指针的引用:
-
引用本身不是一个对象,所以不存在指向引用的指针;
-
指针是对象,所以存在对指针的引用;
-
从右向左阅读r的定义,离变量名最近的符号是&,因此r是一个引用,*说明r引用的是一个指针;
const限定符
- 创建后就不再改变,任何试图给常量赋值的行为都将引发错误;
- const必须初始化;
-
ci的常量特征仅仅在执行改变ci操作时才会发挥作用;
-
默认状态下,const对象仅在文件内有效,当多个文件中出现了同名的const变量,等同于在不同文件中分别定义了独立的变量;
-
const int a = 5;
编译器将在编译过程中把用到该变量的地方都替换为对应的值; -
某些时候,const变量的初始值不是常量表达式,且需要在文件间共享,只在一个文件中定义,在其他多个文件中声明并使用,解决的办法是:const变量不管是声明还是定义,都添加extern关键字;
-
const的引用
-
把引用绑定到const对象上,称之为对常量的引用;
- 初始化和对const的引用
- 初始化常量引用时,允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型;
- 允许为一个常量引用绑定非常量的对象、字面值、表达式;
- 上图中,如果改成
int &ri = dval;
此时ri绑定的是临时量,该操作是非法的;
- 指针和const:
- 指向常量的指针,不能用于改变其所指对象的值,但没有规定那个对象的值不能通过其他途径改变;
- const指针
- 常量指针,把指针本身定为常量,必须初始化,且它的值(存放在指针中的地址)不能再改变;
- 从右向左阅读,离curErr最近的符号是const,说明curErr本身是一个常量对象,*表示curErr是一个指针;
-
顶层const
-
顶层const表示指针本身是个常量;
-
底层const表示指针所指的对象是一个常量;
-
执行对象的拷贝操作时,拷入和拷出的对象必须是具有相同的底层const资格,或者两个对象的数据类型必须能够转换(非常量可以转常量,反之不行);
-
constexpr和常量表达式
-
常量表达式(const expression)是指值不会改变并且在编译过程就能得到计算结果的表达式;
-
字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式;
-
constexpr变量
-
C++11中规定,允许将变量声明为constexpr类型,以便由编译器来验证变量的值是否是一个常量表达式;
-
声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化;
-
constexpr函数:足够简单,编译时就可以计算其结果;
-
字面值类型
-
包括算术类型、引用、指针这类简单、值也显而易见的,不包括自定义类、IO库、string等(不能被定义为constexpr);
-
一个constexpr指针的初始值必须是nullptr或0,或者是存储于某个固定地址中的对象;
-
函数体内定义的变量一般来说并非存放在固定地址中,因此constexpr指针不能指向这样的变量;
-
相反的,定义于所有函数体之外的对象其地址固定不变,能用来初始化constexpr指针;
-
允许函数定义一类有效范围超出函数本身的变量,这类变量和定义在函数体之外的变量一样有固定地址,因此constexpr引用能绑定到这样的变量上,constexpr指针也能指向这样的变量;
-
指针和constexpr
-
在constexpr声明中,如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关;
处理类型
-
类型别名:是某种类型的同义词,用法同原类型;
-
有两种方法定义类型别名:
-
传统的方法是用关键字typedef;
-
新标准规定了新的方法,使用别名声明来定义,使用关键字using;
-
指针、常量和类型别名(太绕了,和直觉理解不一样……)
-
-
auto类型说明符
-
把表达式的值赋值给变量时,让编译器通过初始值来推断表达式的类型;
-
auto定义的变量必须有初始值;
-
auto能在一条语句中声明多个变量,基本数据类型需要相同;
复合类型、常量和auto
-
使用引用,其实是使用引用的对象,被用作初始化值时,真正参与初始化的其实是引用对象的值,编译器以引用对象的类型作为auto的类型;
-
auto一般会忽略掉顶层const,底层const会保留下来;
-
还可以将引用的类型设为auto:
decltype类型指示符
- 有时会希望从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量,c++11引入了类型说明符decltype,作用是选择并返回操作数的数据类型;
- 编译器分析表达并得到它的类型,却不实际调用函数或计算表达式的值;
decltype和引用
自定义数据结构
定义Sales_data类型
- 类体右侧的表示结束必须后面加分号,因为类体后面可以紧跟变量名以表示对该类型的对象的定义;
- 分号表示声明符的结束;
- C++11规定,可以为数据成员提供一个类内初始值,用于初始化数据成员;
预处理器概述
- 确保头文件多次包含仍能安全工作的技术;
- 预处理器是在编译之前执行的一段程序;
- 头文件保护符,是一项预处理功能,依赖于预处理变量;预处理变量有两种状态:已定义和未定义;
- #define指令把一个名字设定为预处理变量,#ifdef和#ifndef分别检查某个指定变量是否已经定义;
- 预处理变量无视C++语言中关于作用域的规则;
- 为了避免与程序中其他实体发生名字冲突,一般把预处理变量的名字全部大写;