C++ Primer:Sec 4, 5, 6
Sec4 表达式、逻辑和关系运算符
-
&&
: 只有左边为真才对右边求值
||
: 只有左边为假才对右边求值-
例子:
index != s.size() && !isspace(s[index])
首先检查index是否达到string对象的末尾,以此才确保只有当index在合理范围之内时,才会计算右侧运算对象的值
-
-
递增递减运算符
-
建议:除非必须,否则不用递增递减运算符的后置版本!!!
-
常用手法:
cout << *iter++ << endl;
-
-
条件运算符
cond ? expr1 : expr2
注意,该运算符的优先级非常低! -
移位运算符优先级
不高不低。比算术运算符低,比关系运算符,赋值、条件运算符高
Sec5 语句
5.1 简单语句
- 空语句
- 分号的使用
- 复合语句
compound statement
5.2 语句作用域
- if, switch, while和for语句的控制结构内定义变量。
定义在控制结构当中的变量只在相应语句的内部可见
5.3 条件语句
- if-else
- switch
记得写break和default
别在case里面定义可能会跨case的变量!
5.4 迭代语句
-
while
-
for循环
for(init-statement; condition; expression) statement;
流程:先初始化,再判断条件,再执行statement,最后执行expression
再判断条件,...,循环往复,直到不满足条件 -
(C++特性)范围for语句
for(declaration : expression) statement
其中,expression必须为一个序列!(有begin和end成员)
declaration定义为一个变量,使得序列中的每个元素都能转换为该变量的类型。
(常常用auto,而且引用是一个好习惯,引用后可以对expression中的序列写操作)- 不能通过范围for语句增加vector对象,因为会存储end()值!!!要是增加就会变得无效了!
-
do-while语句
-
跳转语句
-
break语句
-
continueyuju
-
goto语句
尽量别用goto label;
labeled statement
-
5.6 try语句块和异常处理
-
C++的异常处理:
- throw:
异常检测部分使用throw表达式来表示它遇到了无法处理的问题,我们说throw引发(raise)的异常 - try:
异常处理部分使用try语句块处理异常。
以try开始,以catch结束。(异常处理代码 exception handler) - 一套异常类 exception class
用于在throw表达式和相关的catch子句之间传递异常的具体信息
- throw:
-
throw表达式
if(item1.isbn() != item2.isbn()) throw runtime_error("Data must refer to same ISBN");
-
try语句块
try{ program-statements } catch (exception-declaration) { handler-statements } catch (exception-declaration) { handler-statement; }
Sec6 函数
6.1 函数基础
- 分离式编译
C++开发中广泛使用声明和实现分开的开发形式,其编译过程是分离式编译
就是说各个cpp文件完全分开编译,然后生成各自的obj目标文件,最后通过链接器link生成一个可执行的exe文件。不需其他操作。
6.2 参数传递
形参的类型决定了形参和实参的交互方式
- 引用传递(passed by reference) 形参是引用类型
引用形参也是它绑定对象的别名 - 值传递(passed by valued)
形参和实参是两个独立的对象 - 指针形参
注意函数内部可以通过指针的参数来改变外边的值了
但只改变指针本身,不会影响外边
==
- 当用实参初始化形参的时候会忽略掉顶层const
所以允许用字面值或者非常量,来初始化常量引用
(标准是不要改变常量)
尽量使用常量引用
6.2.4 数组形参
-
管理方法
- 使用标记指定数组长度
- 使用标准库规范
begin,end - 显式传递一个表示数组大小的形参
-
数组形参和const
当函数不需要对数组元素执行写操作,数组形参应该是指向const的指针 -
数组引用形参
f(int &arr[10]); // 错误,不存在引用的数组 f(int (&arr)[10]); // 正确,arr是一个具有10个整数的整型数组的引用
-
多维数组
int *matrix[10]; // 10个指针构成的数组 int (*matrix)[10]; // 指向含有10个整数的数组的指针
6.2.5 main: 处理命令行选项
int main(int argc, char *argv[])
argv为一个数组,元素是指向c风格字符串的指针。第一个形参为argc,是表示数组中字符串的数量!
命令: prog -d -o ofile data0
argv[0]="prog"; // 第一个元素指向程序的名字或者一个空字符串
argv[1]="-d"; // 饥饿下来的元素以此传递命令行提供的参数
argv[2]="-o";
argv[3]="ofile";
argv[4]="data0";
argv[5]=0; // 最后一个指针之后的元素保证为0
6.2.6 含有可变形参的函数
-
编写能处理不同数量实参的函数:
- 如果所有的实参类型相同,可以传递一个名为initializer_list的标准库类型
- 如果实参的类型不同,可以编写一种特殊的函数。即可变参数模板
-
initializer_list
一种标准库类型,用于表示某种特定类型的值的数组initializer_list<T> lst; // 默认初始化,T类型元素的空列表 initializer_list<T> lst{a,b,c...} // lst的元素数量和初始值一样多,lst的元素是对应初始值的副本,列表中迭代元素是const lst2(lst); // 拷贝或者赋值不回拷贝列表中的元素,而是原始列表和副本共享元素 lst2 = lst; lst.size(); lst.begin();lst.end();
initializer_list
永远是常量值 -
省略符形参
仅仅用于C于C++通用的类型!void foo(parm_list,...); // 省略符之后的形参不会执行类型检查 void foo(...);
6.3 返回类型和returen语句
-
返回
- 无返回值函数
- 有返回值函数
-
如何返回值
返回的值用于初始化调用点的一个临时变量 -
返回的注意事项
不要返回局部对象的引用或指针
因为函数完成后,所占用的存储空间也随之被释放掉const string &manip() { string ret; if(!ret.empty()) return ret; // 错误,返回局部对象的引用 else return "empty"; // 错误,empty是一个局部临时量 }
-
返回类类型的函数和调用运算符
调用运算符的优先级和点和箭头运算符相等。且符合左结合律 -
引用返回左值
调用一个返回引用的函数得到左值,其他返回类型为右值// 例子 char &get_val(string &str, string::size_type ix){ return str[ix]; } int main(){ string s("a value"); cout << s << endl; get_val(s, 0) = "A"; cout << s << endl; return 0; }
-
列表初始化返回值
很好用。
返回一个{}起来的值,然后用里面的值来初始化! -
main的返回值
return EXIT_FAILURE; return EXIT_SUCCESS;
6.3.3 返回数组指针
函数可以返回数组的指针或者引用。
typedef int arrT[10];
using arrT = int[10];
arrT* func(int i); // func返回一个指向含有10个整数的数组的指针
int arr[10];
int *p1[10];
int (*p2)[10] = &arr;
或者直接定义
type (*function(parameter_list))[dimension] // 返回数组指针的函数
-
使用尾置返回类型
trailing return type
对返回值比较复杂的函数最有效auto func(int i) -> int(*)[10]; // 把函数返回类型放在->之后,并在前面用auto定义
-
或者用decltype
6.4 函数重载 (重要)
pass
6.5 特殊用途语言特性
-
三种函数相关的语言特性
-
默认实参
一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值
只能省略尾部的实参!!!window = screen(,,'?'); // 错误
而且给定的作用域中,一个形参只能被赋予一次默认实参
只要表达式的类型可以转换为对应类型,该表达式就可以作为默认实参 -
内联函数
-
constexper
-
-
内联函数
可以避免函数调用的开销
inline
-
constexper函数
指能用于常量表达式(编译过程就能得到结果)的函数
函数返回类型以及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句
会被隐式的展开为内联函数 -
建议把内联函数和constexper函数放在头文件里面
6.5.3 调试帮助
头文件保护
-
assert预处理宏
preprocessor marcoassert(expr);
先对expr求值,如果expr为0,则输出信息并终止程序执行。如果为真,则啥也不做
-
NDEBUG预处理变量
如果定义了 NDEBUG,则assert什么也不做。默认状态下没有定义NDEBUG,此时assert将执行运行时检查// 对调试有用的名字 __func__ __FILE__ __TIME__ __LINE__ __DATE__
6.6 函数匹配
void f();
void f(int);
void f(int, int);
void f(double, double = 3.14);
-
确定候选函数和可行函数
candidate function / viable function -
寻找最佳匹配
基本思想是,实参类型与形参类型越接近,匹配得越好 -
含有多个形参的函数匹配
例如:
f(42,2.56)
-
可行函数为f(int,int), f(double, double)
-
确定最佳匹配的条件
- 该函数每个实参的匹配都不劣于其他可行函数所需要的匹配
- 至少有一个实参的匹配优于其他可行函数提供的匹配
-
故该调用具有二义性,拒绝并报错
-
-
实参类型转换
重要!!C/C++类型声明黄金法则
- 步骤:
- 找到变量名,若无变量名,则找到最里边的结构
- 向右看,读出你看到的东西,但是不要跳过括号!
- 再向左看,读出你看到的东西,但也不要跳过括号!
- 如果有括号,跳出一层括号
- 重复上述过程,直到读出最终类型
6.7 函数指针
// 比较两个string对象的长度
bool lengthCompare(const string &, const string &);
// pf指向一个函数,该函数的参数是两个const string的引用,返回值是bool类型
bool (*pf)(const string &, const string &); // 未初始化
一定要写括号!如果不写的话,返回值就是一个指向bool类型的指针
-
使用函数指针
当我们把函数名作为一个值使用的时候,该函数自动地转换成指针。pf = func; pf = &func; // &是可选的。这两条是等价的,而且func应该是bool类型的!返回类型要一致 // 调用 bool b1 = pf("hello", "good"); bool b2 = (*pf)("hello", "good"); // 这两条也是等价的
-
重载函数的指针
通过指针类型决定选用哪个函数。指针类型必须与重载函数中的某一个精确匹配 -
函数指针形参
void useBigger(const string &s1, const string &s2, bool pf(const string &, const string &))
-
可以用decltype和typedef来简化操作!
// Func和Func2是函数类型 typedef bool Func(const string&, const string&); typedef decltype(lengthCompare) Func2; // 等价的类型 // FuncP和FuncP2是指向函数的指针 typedef bool (*FuncP)(const string&, const string&); typedef decltype(lengthCompare) *FuncP2; // 等价声明 void useBigger(const string&, const string&, Func); // 自动将Func转换为指针 void useBigger(const string&, const string&, FuncP2); // 等价
-
-
返回指向函数的指针|
注意,不能返回一个函数!using F = int(int*, int); // F为函数类型 using PF = int(*)(int*, int); // PF为指针类型
PF f1(int); // 正确 F f1(int); // 错误 F *f1(int); // 正确,与第一条等价,返回为指向函数的指针 auto f1(int) -> int (*)(int*, int); // 尾置返回类型