C++ Primer
文|Seraph
推荐序
- C++正在成为一门完美的程序语言,但是代价是增加复杂度。
- C++支持4中不同的编译风格:C风格、基于对象、面向对象、和泛型。
- 其他参考书《C++程序库》、《Effective C++》、《C++ Concurrency in Action》、《Linxu 多线程服务端编程》。
前言
- C11新标准目标
<1> 使语言更为统一,更易于教学;
<2> 使标准库更简单、安全、使用更高效;
<3> 使编写高效的抽象和库变得更简单。
一、开始
-
cout、cerr、clog区别
cerr:标准错误流(非缓冲),指定和显示器关联
clog:标准错误流(缓冲),指定和显示器关联
cout:标准输出流(缓冲),可以重定向到一个文件
非缓冲的意思是没有缓冲,发送给它的内容立即被输出。 -
endl效果是结束当前行,并将与设备关联的缓冲区中的内容刷到设备中。
-
不同的命名空间,使用
using namespace 命名空间
来声明命名空间,如果这两个命名空间有相同的方法,可能会有问题,需要使用作用域运算符::
来指定要使用的命名空间。 -
错误的注释比完全没有注释更糟糕,因为它会误导读者。
-
注释界定符
/* */
不能嵌套。
我们通常需要再调试期间注释掉一些代码。由于这些代码可能包含界定符对形式的注释,因此可能导致注释嵌套错误,因此最好的方式是用单行注释方式注释掉代码段的每一行。 -
当我们使用一个istream对象作为条件时,其效果是检测流的状态。如果流是有效的,即流未遇到错误,那么检测成功,当遇到文件结束符(end-of-file),或遇到一个无效输入时,istream对象的状态会变成无效。
-
输入文件结束符,Windows系统时Ctrl+Z,UNIX系统是Ctrl+D。
-
常见的编译错误有:语法错误、类型错误、声明错误。
一个单个错误常常会具有传递效应,导致编译器在其后报告比实际数量多得多的错误信息。 -
类的作者决定了类类型对象上可以使用的所有操作。
二、变量
- C++是一种静态数据类型语言,它的类型检查发生在编译时。
- 算术类型C++标准规定的尺寸最小值如下:
类型 | 含义 | 最小尺寸 |
---|---|---|
bool | 布尔类型 | 未定义 |
char | 字符 | 8位 |
wchar_t | 宽字符 | 16位 |
char16_t | Unicode字符 | 16位 |
char32_t | Unicode字符 | 32位 |
short | 短整型 | 16位 |
int | 整形 | 16位 |
long | 长整型 | 32位 |
long long | 长整型 | 64位 |
float | 单精度浮点数 | 6位有效数字 |
double | 双精度浮点数 | 10位有效数字 |
long doubel | 扩展精度浮点数 | 10位有效数字 |
-
可寻址的最小内存块称位“字节”,存储的基本单位称为“字word”。
-
类型char和类型signed char并不一样,类型char实际上会表现为signed char和unsigned char中的一种,具体有编译器决定。
-
如何选择类型:
<1> 当明确知道数值不可能为负时,选用无符号类型;
<2> 在算数表达式中不要使用char或bool,char是有符号或无符号是不确定的; -
类型转换
<1> 整数值->浮点类型 如果该整数所占的空间超过浮点类型的容量,精度可能有损失。
<2> 当我们赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。
<3> 当我们赋给有符号类型一个超出它表示范围的值时,结果时未定义的。 -
避免无法预知和依赖于实现环境的行为。
-
算术表达式既有无符号数又有int值时,哪个int值就会转成无符号数。
-
默认情况下,十进制字面值时带符号数,八进制和十六进制字面值既可能时带符号也可能是无符号。
-
多行书写字符串:
cout<<" xxxx"
"xxxx"<<endl;
- 转移序列
转意符 | 含义 |
---|---|
\n | 换行符 |
\v | 纵向制表符 |
\\ |
反斜杠 |
\r | 回车符 |
\t | 横向制表符 |
\b | 退格符 |
? | 问号 |
\f | 进纸符 |
\a | 报警符 |
" | 双引号 |
' | 单引号 |
- 指定字面值的类型
前缀 | 含义 | 类型 |
---|---|---|
u | Unicode16字符 | char16_t |
U | Unicode32字符 | char32_t |
L | 宽字符 | wchar_t |
u8 | UTF-8(仅用于字符串字面常量) | char |
后缀:
后缀 | 最小匹配类型 | 后缀 | 类型 |
---|---|---|---|
u or U | unsigned | f或F | float |
l or L | long | l或L | long double |
ll or LL | long long |
-
如果我们使用列表初始化且初始值存在丢失信息的风险,则编译器将报错。
-
内置类型隐式初始化:定义与任何函数体之外的变量被初始化为0。但是局部静态变量是一个例外。
-
每个类各自决定其初始化对象的方式。
-
任何包含了显示初始化的声明即成为定义,如给由extern关键字标记的变量赋一个初始值,则抵消了extern作用。
-
在函数体内部,如果试图初始化一个由extern关键字标记的变量,将引发错误。
-
C++的标识符由字母、数字和下划线组成,其中必须以字母或下划线开头。对大小字母 敏感。
-
C++关键字
1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|
alignas | alignof | asm | auto | bool |
break | case | catch | char | char16_t |
char32_t | class | const | constexpr | const_cast |
continue | decltype | default | delete | do |
double | dynamic_cast | else | enum | explicit |
export | extern | false | float | for |
friend | goto | if | inline | int |
long | mutable | namespace | new | noexcept |
nullptr | operator | private | protected | public |
register | reinterpret_cast | return | short | signed |
sizeof | static | static_assert | static_cast | struct |
switch | template | this | thread_local | throw |
true | try | typedef | typeid | typename |
union | unsigned | using | virtual | void |
volatile | wchar_t | while |
- C++操作符替代名
1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|
and | bitand | compl | not_eq | or_eq |
xor_eq | and_eq | bitor | not | or |
xor |
-
名字的有效区域始于名字的声明语句,以声明语句所在的作用域末端为结束。
-
当你第一次使用变量时再定义它。
-
复合类型是指基于其他类型定义的类型,常见有的有:引用和指针。
-
引用必须初始化。引用本身不是一个对象,所以不能定义引用的引用。
-
引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起。
-
所有引用的类型都要与之绑定的对象严格匹配。除了以下两种情况例外:
<1>初始化常量引用时,允许用任意表达式作为初始值,只要该表达式的结果能转成引用的类型即可。
<2>可以将基类的指针或引用绑定到派生类对象上。 -
指针与引用区别:
<1>指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象;
<2>指针无须在定义时赋值。 -
因为引用不是对象,没有实际的地址,所以不能定义指向引用的指针。
-
所有指针的类型都要和它指向的对象严格匹配,除了以下两种情况例外:
<1>允许一个指向常量的指针指向一个非常量对象
<2>将基类的指针绑定在派生类对象上 -
指针的值应属于下列4种状态:
<1> 指向一个对象
<2> 指向紧邻对象所占空间的下一个位置
<3> 空指针
<4> 无效指针
对于无效指针,编译器并不负责检查此类错误,因此程序员必须清楚任意给定的指针是否有效。2、3状态如果直接访问也是会出问题的。 -
不能直接操作void*指针所指的对象,因为我们不知道这个对象到底是什么类型。
-
如果给定一个指针,我们是无法判断它是否指向一个合法的对象的。
-
复合类型声明建议将修饰符和变量标识符写在一起。
-
理解复合类型声明的多个修饰符含义,从右向左读,离变量名最近的符号对变量的类型有最直接的影响。
int *&r = p; //r是一个队指针p的引用
-
默认状态下,const对象仅在文件内有效。所以当我们需要一份定义,多个文件使用时,可以对const变量不管是声明还是定义都添加extern关键字。(VS编译器定义也可以用const,只要声明用extern就可以)
-
编译器将在编译过程中把用到该变量的地方都替换成对应的值。
-
一个常量引用被绑定到另外一个类型上不会出问题,如下:
double dval = 3.14;
const int &ri = dval;
实际上const int &ri = dval;
等同于下面语句:
const int temp = dval;
const int &ri = temp;
ri绑定了一个临时量,但是如果是非常量引用,类型不一致则会报错,因为我们不可能去改变临时变量。
- 对const的引用可能引用一个并非const的对象
int i=32;
const &r=i;
int i=33;
如上,改变i即能改变r,但是不能直接给r赋值。
- 顶层const表示指针本身是一常量,而用底层const表示指针所指的对象是一个常量。
const int ci = 42; //顶层const
const int *p1 = &ci; //底层const
-
当执行对象的拷贝操作时,顶层const不受影响,但是拷入和拷出得对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。
-
常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。字面值和用常量表达式初始化的const对象也是常量表达式。
一个对象是不是常量表达式由它的数据类型和初始值共同决定。
如下sz就不是一个常量表达式,它的具体值直到运行时才能获取到:
int staff_size = 27;
const int sz = get_size();
-
如果你认定变量是一个常量表达式,那就把它声明成constexpr类型,编译器会验证变量值是否是一个常量表达式。
-
限定符consterxpr仅对指针有效,与指针所指的对象无关:
const int *p = nullptr; //p是一个指向整形常量的指针
constexpr int *q = nullptr; //q是一个指向整数的常量指针
- 类型别名
typedef char *pstring;
const pstring cstr = 0; //cstr是指向char的常量指针
其中const pstring cstr = 0;
不等同于const char *cstr = 0;
-
auto一般会忽视掉顶层const,同时底层const则会保留下来。
但设置一个类型为auto的引用时,初始值中的顶层常量属性仍然保留。 -
与decltype返回该变量的类型,包括顶层const和引用在内。
引用从来都作为其所指对象的同义词出现,只有用在decltype处是一个例外。 -
如果表达式的内容是解引用操作,则decltype将得到引用类型。
-
decltype((variable))的结果永远是引用。
三、字符串、向量和数组
- 位于头文件的代码一般来说不应该使用using声明。
- 初始化string对象的方式
初始化方式 | 含义 |
---|---|
string s1 | 默认初始化,s1是一个空串 |
string s2(s1) | s2是s1的副本 |
string s2 = s1 | 等价于s2(s1) |
string s3("value") | s3是字面值"value"的副本 |
string s3 = "value" | 等价于s3("value") |
string s4(n, 'c') | 把s4初始化为由连续n个字符c组成的串 |
-
string从标准输入读取时,会自动忽略开头的看空白(即空格符、换行符、制表符等)。
-
string的操作
代码 | 含义 |
---|---|
os<<s | 将s写到输出流os中 |
is>>s | 从is中读取字符串赋给s |
geline(is, s) | 从is中读取一行赋给s,读取的字符不包括换行符 |
s.empty() | s为空返回true |
s.size() | 返回s中字符的个数 |
s[n] | 返回s中第n个字符的引用 |
s1+s2 | 返回s1和s2连接后的结果 |
s1=s2 | 用s2的副本替代s1中原来的字符 |
s1==s2 | 如果s1和s2中所含的字符完全一样,则它们相等 |
s1!=s2 | 等性判断对字符的大小写敏感 |
<,<=,>,>= | 利用字符在字典中的顺序进行比较 |
-
size函数返回的是一个size_type类型的值,它其实是一个无符号整形,所以使用时一定要注意不能与负值比较,比较值会自动转换成一个比较大的无符号值。
所以一条表达式中已经有了size()函数就不要再用int了。
要使用size_type,首先得指定类型。 -
string对象与字面值相加,必须保证每个加法运算符的两侧的运算对象至少有一个是string。
-
cctype头文件中的函数
函数 | 含义 |
---|---|
isalnum(c) | 当c是字母或数字时为真 |
isalpha(c) | 当c是字母时为真 |
iscntrl(c) | 当c是控制字符时为真 |
isdigit(c) | 当c是数字时为真 |
isgraph(c) | 当c不是空格但可打印时为真 |
islower(c) | 当c是小写字母时为真 |
isprint(c) | 当c是可打印字符时为真(即c是空格或c具有可视形式) |
ispunct(c) | 当c是标点符号时为真(即c不是控制字符、数字、字母、可打印空白中的一种) |
isspace(c) | 当c是空白时为真(即c时空格、横向制表符、纵向制表符、回车符、换行符、进纸符中的一种) |
isupper(c) | 当c是大写字母时为真 |
isxdigit(c) | 当c是十六进制数字时为真 |
tolower(c) | 如果c是大写字母,输出对应的小写字母;否则原样输出c |
toupper(c) | 如果c是小写字母,输出对应的大写字母;否则原样输出c |
-
C语言的头文件形如name.h,C++则将这些文件命名为cname,即c表示这是一个属于c语言标准库的头文件。名为cname的头文件中定义的名字从属于命名空间std,而定义在名.h的头文件中的则不然。
-
初始化vector对象的方法
代码 | 含义 |
---|---|
vector<T> v1 | v1是一个空vector,它潜在的元素是T类型的,执行默认初始化 |
vector<T> v2(v1) | v2中包含有v1所有元素的副本 |
vector<T> v2 = v1 | 等价于v2(v1),v2中包含有v1所有元素的副本 |
vector<T> v3(n, val) | v3包含了n个重复的元素,每个元素的值都是val |
vector<T> v4(n) | v4包含了n个重复地执行了值初始化的对象 |
vector<T> v5{a,b,c...} | v5包含了初始值个数的元素,每个元素被赋予相应的初始值 |
vector<T> v5={a,b,c...} | 等价于v5{a,b,c...} |
-
各种初始化方式不能等价的情况
<1>使用拷贝初始化时,只能提供一个初始值;
<2>如果提供的是一个类内初始值,则只能使用拷贝初始化或使用花括号的形式初始化;
<3>如果提供的是初始元素值得列表,则只能把初始值都放在花括号里进行列表初始化,而不能放在圆括号里。 -
对于大括号的列表初始化,只有在无法执行列表初始化时才会考虑其他初始化方式。
vector<string> v8{10, "hi"}; //v8有10个值为“hi”的元素
-
如果循环体内部包含有向vector对象添加元素的语句,则不能使用范围for循环。范围for语句体内不应改变其所遍历序列的大小。
-
vector支持的操作
操作 | 含义 |
---|---|
v.empty() | 如果v不含有任何元素,返回真 |
v.size() | 返回v中元素的个数 |
v.push_back(t) | 向v的尾端添加一个值为t的元素 |
v[n] | 返回v中第n个位置上元素的引用 |
v1 = v2 | 用v2中元素的拷贝替换v1中的元素 |
v = {a, b, c...} | 用列表中元素的拷贝替换v1中的元素 |
v1 == v2 | v1和v2相等当且仅当它们的元素数量相同且对应位置的元素都相同 |
v1 != v2 | 不相等 |
<, <=, >, >= | 以字典顺序进行比较 |
-
end成员则负责返回指向容器“尾元素的下个位置”的迭代器,如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器。
-
任何一种可能改变vector对象容量的操作,都会使该vector对象的迭代器失效。
-
vector和string迭代器运算(相差都是指元素个数)
·操作 |
---|
iter+n |
iter-n |
iter1+=n |
iter1-=n |
iter1-iter2 |
>、>=、 <、<= |
-
两个迭代器的距离类型为difference_type的带符号整形数。
-
定义数组的时候必须指定数组的类型,不允许用auto关键字由初始值的列表推断类型。
-
数组不允许直接拷贝和赋值。
-
数组下标使用的是size_t类型,是一种机器相关的无符号类型。
-
如下decltype返回的是5个整数构成的数组。
int ia[] = {0,1,2,3,4};
decltype(ia) = {5,6,7,8,9};//如写成decltype(ia) = {5,6,7,8,9,10}则会报error C2078:初始值设定太多
-
两个指针相减的结果的类型为ptrdiff_t的带符号类型。
-
标准库类型限定使用的下标必须是无符号类型,而内置的下标运算无此要求。
-
C风格字符串的函数
函数 | 作用 |
---|---|
strlen(p) | 返回p的长度,空字符不计算在内 |
strcmp(p1,p2) | 比较p1和p2的相等性 |
strcat(p1,p2) | 将p2附加到p1之后,返回p1 |
strcpy(p1,p2) | 将p2拷贝给p1,返回p1 |
-
最好使用标准库string,而不要使用C风格字符串,因为C风格字符串不仅使用起来不太方便,而且极易引发程序漏洞,是诸多安全问题的根本原因。
-
c_str函数返回一个C风格的字符串,但是无法保证返回的数组一直有效,最好将该数组重新拷贝一份。
-
允许使用数组来初始化vector对象
int int_arr[] = {0, 1, 2, 3, 4, 5};
vector<int> ivec(begin(int_arr), end(int_arr));
- 要使用范围for语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。
四、表达式
- 表达式由一个或多个运算对象组成,字面值和变量是最简单的表达式。
- 重载运算符时,运算对象的类型和返回值得类型,都是由该运算符定义的;但是运算对象个数、运算符的优先级和结合律都是无法改变的。
- 需要右值的地方可以用左值代替,但是不能右值当成左值。
当一个左值被当成右值使用时,实际使用的是它的值。
例外:470页 - 如果表达式的求值结果是左值,decltype作用于该表达式(不是变量)得到一个引用类型。
- decletype(&p)的结果是int**
- 几种用到左值的运算:
<1>赋值运算符需要一个(非常量)左值作为其左侧运算对象,得到的结果也仍然是一个左值。
<2>取地址符作用于一个左值运算对象,返回一个指向该运算对象的指针,这个指针是一个右值。
<3>内置解引用,下标运算的求值结果都是左值。 - 只有4中运算符明确规定了运算对象的求值顺序:
&&
、||
、?:
、,
- 运算对象的求值顺序与优先级和结合律无关。
- 如果改变了某个运算对象的值,在表达式的其它地方不要再使用这个运算对象。但当改变运算对象的子表达式本身就是另外一个子表达式的运算对象时则可以。如
*++iter
。 - 逻辑和关系运算符运算结果都是右值。使用短路求值法。
- 赋值运算符的优先级低于关系运算符的优先级
- 递增递减运算符,前置版本将对象本身作为左值返回,后置版本则将对象原始值的副本作为右值返回。
- 尽量使用前置版本的递增递减运算符。
- 后置递增运算符的优先级高于解引用运算符。
- 解引用运算符的优先级低于点运算符。
- 条件运算符满足右结合律
- 关于符号位如何处理没有明确规定,所以强烈建议仅将位运算符用于处理无符号类型。
- 无符号类型不小于带符号类型,那么带符号类型的运算对象转成无符号的。
- 当数组被用作decltype关键字的参数,或者作为取地址符(&)、sizeof及typeid等运算符的运算对象时,数组指针不会转成指针。
- 强制转换类型
<1>static_cast:任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。
<2>const_cast:只能改变运算对象的底层const。.
<3>reinterpret_cast:通常作为运算对象的位模式提供较低层次上的重新解释。 - 运算符优先级
运算符 | 描述 |
---|---|
第一级别 | |
:: | 作用域解析符 |
第二级别 | |
() | 函数调用、成员初始化 |
[] | 数组数据获取 |
-> | 指针型成员调用 |
. | 对象型成员调用 |
++ | 后自增运算符 |
-- | 后自减运算符 |
const_cast | 特殊属性转换 |
dynamic_cast | 特殊属性转换 |
static_cast | 特殊属性转换 |
reinterpret_cast | 特殊属性转换 |
typeid | 对象类型符 |
第三级别(具有右结合性) | |
! | 逻辑取反 |
not | ! 的另一种表达 |
~ | 按位取反 |
compl | ~的另一种表达 |
++ | 预自增运算符 |
-- | 预自减运算符 |
- | 负号 |
+ | 正号 |
* | 指针取值 |
& | 值取指针 |
new | 动态元素内存分配 |
new [] | 动态数组内存分配 |
delete | 动态析构元素内存 |
delete [] | 动态析构数组内存 |
(type) | 强制类型转换 |
sizeof | 返回类型内存 |
第四级别 | |
->* | 类指针成员引用 |
* | 类对象成员引用 |
第五级别 | |
* | 乘法 |
/ | 除法 |
% | 取余数(模运算) |
第六级别 | |
+ | 加法 |
- | 减法 |
第七级别 | |
<< | 位左移 |
>> | 位右移 |
第八级别 | |
< | 小于 |
<= | 小于等于 |
> | 大于 |
>= | 大于等于 |
第九级别 | |
== | 恒等于 |
eq | == 的另一种表达 |
!= | 不等于 |
not_eq | !=的另一种表达 |
第十级别 | |
& | 位且运算 |
bitand | &的另一种表达 |
第十一级别 | |
^ | 位异或运算 |
xor | ^的另一种表达 |
第十二级别 | |
| | 位或运算 |
bitor | 的另一种表达 |
第十三级别 | |
&& | 逻辑且运算 |
and | &&的另一种表达 |
第十四级别 | |
or | 逻辑或运算 |
第十五级别(具有右结合性) | |
? : | 条件运算符 |
第十六级别(具有右结合性) | |
= | 赋值 |
+= | 加赋值运算 |
-= | 减赋值运算 |
*= | 乘赋值运算 |
/= | 除赋值运算 |
%= | 模赋值运算 |
&= | 位且赋值运算 |
and_eq | &= 的另一种表达 |
^= | 位异或赋值运算 |
xor_eq | ^=的另一种表达 |
or_eq | 位或赋值运算 |
<<= | 位左移赋值运算 |
>>= | 位右移赋值运算 |
第十七级别 | |
throw | 异常抛出 |
第十八级别 | |
, | 逗号分隔符 |
- 不能重载的运算符:
::
、.
、sizeof
、?:
、*
、throw
、typeid
、const_cast
、dynamic_cast
、static_cast
、reinterpret_cast
。
五、语句
- 使用空语句时,应该加上注释,从而让人知道时有意省略的。
- else与离它最近的尚未匹配的if匹配。
- switch语句
<1>case标签必须是整型常量表达式;
<2>即使默认不做任何事,也最好定义一个default,告诉代码读者我们考虑了默认情况;
<3>每条case后面一定要注意break是否添加; - 不允许跨过变量的初始化语句直接跳转到改变量作用域内的另一个位置。switch和go语句都可能出现这种情况,需要注意。
case语句可以定义语句块,使不在一个作用域范围内即可。 - for语句中初始化语句可以定义多个对象,但是只能有一条声明语句。
- goto语句和控制权转向的那条带标签的语句必须位于同一个函数之内。
- goto跳回到变量定义之前意味着系统将销毁该变量,然后重新创建它。
- 异常被抛出时,逐级往外找匹配的catch子句,如没有找到,则转到名为terminate的标准库。
- stdexcept定义的异常类
异常 | 解析 |
---|---|
exception | 最常见的问题 |
runtime_error | 只有在运行时才能检测出问题 |
range_error | 运行时错误:生成的结果超出了有意义的值域范围 |
overflow_error | 运行时错误:计算上溢 |
underflow_error | 运行时错误:计算下溢 |
logic_error | 程序逻辑错误 |
domain_error | 逻辑错误:参数对应的结果值不一致 |
invalid_argument | 逻辑错误:无效参数 |
length_error | 逻辑错误:试图创建一个超出该类型最大长度的对象 |
out_of_range | 逻辑错误:使用一个超出有效范围的值 |
- 标准库异常类(exception、bad_alloc、bad_cast)只能使用默认初始化,不允许为这些对象提供初始值。其他异常类型的行为则相反,必须提供初始值。
六、函数
- 为了与C语言兼容,可以使用关键字void表示函数没有形参。
- 拷贝大的类类型对象或容器对象比较低效,甚至有的类类型(包括IO类型在内)根本就不支持拷贝操作。这个时候,只能通过引用形参访问该类型的对象。
- 实例化形参时会忽略掉顶层const,所以下面函数属于重复定义:
void fcn(const int i){};
void fcn(int){};
- 尽量使用常量引用
如下面代码,编译就会出错,但当我们find_char第一参数改为常量引用就没有问题了,同时即使调用使用非常量实参也不影响。
string::size_type find_char(string &s, char c, string::size_type &occurs);
bool is_sentence(const string &s)
{
string::size_type ctr = 0;
return find_char(s, '.', ctr) == s.size() - 1 && ctr == 1;
}
- const_cast和重载
const string &shorterString(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1:s2;
}
如上代码,如果两个非常量的string实参调用这个函数,但返回的结果仍然是const string,我们可能需要返回非const的string。所以需要重载一个新的shorterString函数:
string &shorterString(string &s1, string &s2)
{
auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));
return const_cast<string&>(r);
}
当我们传递一个非常量对象或者指向非常量对象的指针时,编译器会优先选择非常量版本的函数。同时需要注意的是,const_cast除了去掉const,还可以加上const。
-
在不同的作用域中无法重载函数名,只会隐藏。
-
尽量让经常使用默认值的形参出现在参数列表后面。
-
一个函数可以多次声明,但是在给定的作用域中一个形参只能被赋予一次默认实参。
-
局部变量不能作为默认实参:
sz wd = 80;
char def = ' ';
sz ht();
string screen(sz = ht(), sz = wd, char = def);
void f2()
{
def = '*';
sz wd = 100;
window = screen(); //调用 screen(ht(), 80, '*'),之所以第二个参数时80,
//是因为f2函数内的wd是重新定义的局部变量,跟传给screen的默认实参有关系。
}
-
内联说明只是向编译器发出的一个请求,编译器可以选择忽略这个请求。
-
编译器会把constexpr函数的调用替换成其结果值,所以constexpr函数体内的语句在运行时不执行任何操作,constexpr函数约定:
<1>函数的返回类型及所有形参的类型都得是字面值类型
<2>函数体中必须有且只有一条return语句 -
assert预处理宏由预处理器而非编译器管理,如果参数为假,则输出洗脑洗并终止程序的执行。
如定义了NDEBUG,assert不做任何处理。 -
一些预处理变量(大小写敏感)
预处理变量 | 含义 |
---|---|
__File__ | 存放文件名的字符串字面值 |
__LINE__ | 存放当前行号的整型字面值 |
__TIME__ | 存放文件编译时间的字符串字面值 |
__DATE__ | 存放文件编译日期的字符串字面值 |
__func__ | 存放函数的名字 |
-
实参类型与形参类型越接近,它们匹配得越好。如果没有任何一个函数脱颖而出,编译器将报二义性调用的信息。
实参类型到形参类型的转换等级:
<1>精确匹配,包括以下情况:
实参类型和形参类型相同
实参从数组类型或函数类型转换成对应的指针类型
向实参添加顶层const或者从实参中删除顶层const
<2>通过const转换实现的匹配
<3>通过类型提升实现的匹配
<4>通过算术类型转换或指针转换
<5>通过类类型转换实现的匹配 -
算术类型转换的级别都一样。
如下manip的调用会出现二义性。
void mainip(long);
void main(float);
manip(3.14);
-
函数的类型由它的返回类型和形参类型共同决定,与函数名无关。
在指向不同函数类型的指针间不存在转换规则。必须精确匹配。 -
牢记decltype返回函数类型,不会将函数类型自动转换成指针类型。
七、类
- 优秀的类设计者也应该密切关注那些有可能使用该类的程序员的需求。
- const成员函数不能改变调用它的对象的内容。
- 类编译:首先编译成员的声明,然后才轮到成员函数体,所以成员函数体可以随意使用类中的其它成员而无须在意这些成员出现的次序。
- ·读取和写入的操作会改变流的内容。
- 一般来说,执行输出任务的函数应该尽量减少对格式的控制,这样可以确保由用户来决定是否换行。
- 构造函数不能被声明为const,当我们创建类的一个const对象时,直至构造函数完成初始化过程,对象才能真正取得其常量属性。因此,构造函数在const对象的构造过程中可以向其写值。
- 合成的默认构造函数按照如下规则初始化类的数据成员:
<1> 如果存在类内的初始值,用它来初始化成员;
<2> 否则默认初始化该成员。 - 编译器只有在发现类不包含任何构造函数的情况下才会替我们生产一个默认的构造函数。
- 定义在块中的内置类型或复合类型的对象