自己写的C++11 Primer Plus 学习笔记,如有雷同不胜荣幸,如有错误敬请指正
1. 关系表达式与分支语句
1. 逗号运算符
cata = 17,240;复制代码
- 1
被解释为
(cata = 17),240;复制代码
- 1
即,将cata设置为17,而240将不起作用,但是
cats = (17,240);复制代码
- 1
由于括号的优先级最高,该表达式将把cats设置为240(逗号右边的表达式)
2. 类型别名
相同点:
#define BYTE char
typedef char byte复制代码
- 1
- 2
- 3
区别:
typedef char * byte_pointer
byte_pointer pa,pb <<==>> char * pa,*pb //pa,pb a pointer to char
#define FLOAT_POINTER float *
FLOAT_POINTER pa,pb <<==>> float *pa,pb //pa a pointer to float,pb just a float复制代码
- 1
- 2
- 3
- 4
- 5
3. cctype中的字符函数
函数名称 | 返回值 |
---|---|
isalnum() | 如果参数是字母数字,即字母或数字,该函数返回true |
isalpha() | 如果参数是字母,该函数返回true |
iscntrl() | 如果参数是控制字符,该函数返回true |
isdigit() | 如果参数是数字(0~9),该函数返回true |
isgraph() | 如果参数是除空格之外的打印字符,该函数返回true |
islower() | 如果参数是小写字母,该函数返回true |
isprint() | 如果参数打印字符(包括空格),该函数返回true |
ispunct() | 如果参数是标点符号,该函数返回true |
isspace() | 如果参数是标准空白字符,如空格,换行符,回车,水平制表符,垂直制表符等,该函数返回true |
isupper() | 如果参数是大写字母,该函数返回true |
isxdigit() | 如果参数是十六进制数字,即0~9,a~f或A~F,该函数返回true |
tolower() | 如果参数是大写字符,则返回其小写,否则返回该参数 |
toupper() | 如果参数是小写字符,则返回其大写,否则返回该参数 |
4. 文本文件与控制台输入
控制台输入:
- 必须包含头文件 iostream
- 头文件iostream定义了一个用于处理输入的istream类
- 头文件iostream声明了一个名为cin的istream变量(对象)
- 必须指明名称空间std
- 可以结合使用cin和运算符 >> 来读取各种类型的数据
- 可以使用 cin 和 get() 方法来读取一个字符,使用 cin 和 getline() 来读取一行字符
- 可以结合使用 cin 和 eof() ,fail() 方法来判断输入是否成功
- 对象 cin 本身被用作测试条件时,如果最后一个读取操作成功,它将被转换为布尔值 true,否则被转换为 false
文件输出:
- 必须头文件fstream
- 头文件 fstream 定义了一个用于处理输入的 ifstream 类
- 需要声明一个或多个 ifstream 变量(对象),并以自己喜欢的方式对其进行命名,条件是遵守常用的命名规则
- 必须指明名称空间 std
- 需要将 ifstream 对象与文件关联起来。为此,方法之一是使用 open() 方法
- 使用完文件后,应使用 close() 方法将其关闭
- 可结合使用 ifstream 对象和运算符 >> 来读取各种类型数据
- 可以使用 ifstream 对象和 get() 方法来读取一个字符,使用 ifstream 对象和 getline() 来读取一行字符
- 可以结合使用 ifstream 和 eof(), fail() 等方法来判断输入是否成功
- ifstream 对象本身被用作测试条件时,如果最后一个读取操作成功,它将被转换为布尔值 true
2. 函数–C++的编程模块
通常,函数通过将返回值复制到指定的 CPU 寄存器或内存单元中来将其返回。随后,调用程序将查看该内存单元。返回函数和调用函数必须就该内存单元中储存的数据类型达成一致。函数原型将返回值类型告知调用程序,而函数定义命令被调用函数应返回什么类型的数据
1. 函数原型
原型描述了函数到编译器的接口,也就是说,它将函数返回值的类型以及参数的类型和数量告诉编译器
如下述函数调用
double volume = cube(side);复制代码
- 1
- 首先,原型告诉编译器,cube() 有一个 double 参数。如果程序没有提供这样的参数,原型将编译器能够捕获这种错误。
- 其次,cube() 函数完成计算后,将把放置在指定的位置–可能是CPU的寄存器,也可能是内存中。
- 然后调用函数将从这个位置获得返回值。
- 由于原型指出了 cube() 的类型为 double ,因此编译器知道应检索多少个字节以及如何解释它们。
原型应确保以下几点:
- 编译器正确处理函数返回值
- 编译器检查使用的参数数目是否正确
- 编译器检查使用的参数类型是否正确。如果不正确,则转换为正确的类型(可能的话)
cheers(cube(2));复制代码
- 1
- 首先,程序将 int 的值 2 传递给 cube(),而后者期望的是 double 类型。编译器注意到,cube() 原型指定了一个 double 类型参数,因此将 2 转换为 2.0 .
- 接下来,cube() 返回一个 double 值,这个值被用作 cheers() 的参数。编译器再次检查原型,并发现 cheers() 要求一个 int 参数,因此它将返回值转换为整数 8。
- 通常,原型自动将被传递的参数强制转换为期望的类型。
在 ANSI C 中,括号为空意味着不指出参数—这意味着将在后面定义参数列表。在 C++ 中,不指定参数列表时应使用省略号:
void say_bye(...);//C++ abdication of responsibility复制代码
- 1
2. 指针
可行(即,常量赋给常量,const 赋给 const):
1. 将常规变量的地址赋给常规指针
2. 将 const 变量的地址赋给指向 const 的指针
注意: 如果数据类型本身并不是指针,则可以将 const 数据或非 const 数据的地址赋给指向 const 的指针,但只能将非 const 数据的地址赋给非const 指针。
尽可能使用const(将指针参数声明为指向常量数据的指针的理由):
- 这样可以避免由于无意间修改数据而导致的变成错误
- 使用 const 使得函数能够处理 const 和 非 const 实参,否则将只能接受非 const 数据
const int * ps = &sloth; // a pointer to const int int * const finger = &sloth; // a const pointer to int复制代码
- 1
- 2
第一个声明不允许使用 ps 来修改 sloth 的值,但允许将 ps 指向另一个位置
第二个声明使得 finger 只能指向 sloth,但允许使用 finger 来修改 sloth 的值
即,finger 和 *ps 都是 const,而 *finger 和 ps 不是
函数指针::
①. 获取函数地址
获取函数地址很简单:只要使用函数名(后不跟参数)即可。也就是说,如果 think() 是一个函数,则 think 就是该函数的地址。
②声明函数指针:
声明指向函数的指针时,必须指定指针指向的函数类型。这意味着声明应指定函数的返回类型以及函数的特征标
double pam(int);
double (*pf) (int);
pf = pam; //pf now points to the pam() function
double x = pam(4); //call pam() using the function name
double y = (*pf)(5); //call pam() using the pointer pf复制代码
- 1
- 2
- 3
- 4
- 5
二维数组::
ar2[r][c] == *(*(ar2 + r) + c); //same thing
//******************
ar2 //pointer to first row of an array of 4 int
ar2 + r //pointer to row r (an array of 4 int)
*(ar2 + r) //row r (an array of 4 int,hence the name of an array,
//thus a pointer to the first int in the row; eg: ar2[r]
*(ar2 + r) + c //pointer int number c in row r; eg: ar2[r] + c
*(*(ar2 + r) + c) //value of int number c in row r; eg: ar[r][c]复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
3. 函数探幽
1. C++ 内联函数
- 在函数声明前加上关键字 inline;
- 在函数定义前加上关键字 inline;
函数调用:
- 对于普通函数,执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈,跳到标记函数起点的内存单元,执行函数代码,然后跳回到地址被保存的指令处。
- 对于内联函数,编译器将使用相应的函数代码替换函数调用。对于内联代码,程序无需跳到另一个位置处执行代码,再条回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。
2. 引用变量
引用变量的主要作用是用作函数的形参
返回引用最重要的一点是,应避免返回函数终止时不再存在的内存单元引用
① 创建引用变量(必须在声明引用时将其初始化)
C++ 使用 &
来声明引用,int & 指的是指向 int 的引用
int rats = 101;
int & rodents = rats; ==>> int * const pr = &rats;
int * prats = &rats;复制代码
- 1
- 2
- 3
表达式 rodents
与 *prats
都可以同rats
互换,而表达式 &rodents
与 prats
都可以同 &rats
互换。
② 将引用用作函数参数(按引用传递)
//success
void swapr(int & a,int & b) //use references
{
int tmp;
tmp = a; //use a,b for values of variables
a = b;
b = tmp;
}
//success
void swapp(int * p,int * q) //use pointers
{
int tmp;
tmp = *p; //use *p,*q for values of variables
*p = *q;
*q = tmp;
}
//fail
void swapv(int a,int b) //try using values
{
int tmp;
tmp = a; //use a,b for values of variables
a = b;
b = tmp;
}
//******************
swapr(wallet1,wallet2); //pass variables
swapp(&wallet1,&wallet2); //pass addresses of variables
swapv(wallet1,wallet2); //pass values of variables复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
内在区别: 在 swapr() 中,变量 a 和 b 是wallet1 和 wallet2 的别名,所以交换 a 和 b 的值相当于交换 wallet1 和 wallet2 的值;但在 swapv() 中,变量 a 和 b 是复制了 wallet1 和 wallet2 的值的新变量,因此交换 a 和 b 的值并不会影响 wallet1 和 wallet2 的值。
如果程序员的意图是让函数使用传递给它的信息,而不对这些信息进行修改,同时又想使用引用,则应使用常量引用。
临时变量,引用参数和const::
如果实参与引用参数不匹配,C++ 将生成临时变量。当前,仅当参数为 const 引用时,C++才允许这样做。
如果引用参数是 const,则编译器将在下面两种情况生成临时变量:
- 实参的类型正确,但不是左值
- 实参的类型不正确,但可以转换为正确的类型
long a = 3,b = 5;
swapr(a,b); //swapr(int & a,int & b)表示上面的函数复制代码
- 1
- 2
这里是类型不匹配,因此编译器将创建两个临时 int 变量,将它们初始化为 3 和 5,然后交换临时变量的内容,而 a 和 b 保持不变。
简而言之,如果接受引用参数的函数的意图是修改作为参数传递的变量,则创建临时变量将阻止这种意图的实现。解决方法是禁止创建临时变量。
注意: 如果函数调用的参数不是左值或与相应的 const 引用参数的类型不匹配,则 C++ 将创建类型正确的匿名变量,将函数调用的参数的值传递给该匿名变量,并让参数来引用该变量。
尽可能使用 const:
- 使用 const 可以避免无意中修改数据的编程错误
- 使用 const 使函数能够处理 const 和非 const 实参,否则将只能接受非 const 数据
- 使用 const 引用使函数能够正确生成并使用临时变量
右值引用:使用
&&
声明
何时使用引用参数::
① 使用引用参数的主要原因:
- 程序员能够修改调用函数中的数据对象
- 通过传递引用而不是整个数据对象,可以提高程序的运行速度
② 何时使用引用,指针和按值传递:
Ⅰ 对于使用传递的值而不做修改的函数:
- 如果数据对象很小,如内置数据类型或小型结构,则按值传递
- 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向 const 的指针
- 如果数据对象是较大的结构,则使用 const 指针或 const 引用,以提高程序的效率
- 如果数据数据对象的类对象,则使用 const 引用。传递类对象的标准方式是按引用传递
Ⅱ 对于修改调用函数中数据的函数:
- 如果数据对象是内置数据类型,则使用指针。如果看到诸如 fixit(&x) 这样的代码(其中 x 是 int ),则很明显,该函数将修改 x
- 如果数据对象是数组,则只能使用指针
- 如果数据对象是结构,则使用引用或指针
- 如果数据对象是类对象,则使用引用
3. 函数模板
由于模板允许以泛型的方式编写程序,因此有时也会被称为通用编程。由于类型是由参数表示的,因此模板特性有时也被称为参数化类型
template<typename AnyType>
void Swap(AnyType &a,AnyType &b)
{
AnyType tmp;
tmp = a;
a = b;
b = tmp;
}复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
第一行指出,要建立一个模板,并将类型命名为 AnyType 。可以使用 class 代替 typename。
(模板函数不能缩短可执行程序)
① 显示具体化::
显示具体化标准:
- 对于给定的函数名,可以有非模板函数,模板函数和显示具体化模板函数以及它们的重载版本
- 显示具体化的原型和定义应以 template<> 打头,并通过名称来指出类型
- 具体化优先于常规模板,而非模板函数优先于具体化和常规模板
//no template function prototype
void swap(job &,job &);
//template function prototype
template<typename T>
void swap(T &,T &);
//explicit specialization for the job type
template <> void swap<job>(job &,job &);复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
② 实例化和具体化::
当调用 swap(i,j)
(i,j 都为 int 类型)时,编译器会生成一个 swap() 实例。模板并非函数定义,但使用 int 的模板实例是函数定义,这种实例化方式被称为隐式实例化
显示实例化: template void swap<int>(int,int)
试图在同一个文件(或转换单元)中使用同一种类型的显示实例和显示具体化将出错
隐式实例化,显示实例化和显示具体化统称为具体化
③ 重载解析
步骤:
- 第一步: 创建候选函数列表。其中包含与被调用函数名称相同的函数和模板函数
- 第二步: 使用候选函数列表创建可行函数列表。这些都是函数数目正确的函数,为此有一个隐式转换序列,其中包含实参类型与相应形参类型完全匹配的情况
- 第三步: 确定是否有最佳的可行参数。如果有,则使用它,否则该函数调用出错
void may(int); //#1
float may(float,float = 3); //#2
void may(char); //#3
char * may(const char *); //#4
char may(const char &); //#5
template<class T> void may(const T &); //#6
template<class T> void may(T *); //#7复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
其中 #4 和 #7 不可行,因为整数类型不能被隐式的转换(即没有显示强制类型转换)为指针类型。
编译器确定函数最佳顺序:
- 完全匹配,但常规函数优先于模板
- 提升转换(例如:char 和 short 自动转换为 int,float 自动转换为 double)
- 标准转换(例如:int 转换为 char,long 转换为 double)
- 用户定义的转换,如类声明中定义的转换
补充:
- 指向非 const 的指针和引用优先于指向 const 的指针和引用
- 非模板函数将优先于模板函数
- 显示具体化将优先于使用模板隐式生成的具体化
函数 #1 优于函数 #2,因为 char 到 int 的转换是提升转换,而char 到 float 的转换是标准转换。函数 #3,函数 #5和函数 #6 都优于函数 #1 和 #2,因为它们都是完全匹配的。#3 和 #5 优于 #6,因为 #6 是模板函数。
从实参 | 到形参 |
---|---|
Type | Type & |
Type & | Type |
Tyep [] | *Type |
Type(argument-list) | Type(*) (argument-list) |
Type | const Type |
Type | volatile Type |
Type * | const Type |
Type * | volatile Type * |
④ 模板函数的发展
template<class T1,class T2>
void ft(T1 x,T2 y)
{
...
decltype(x + y) xpy = x + y;
...
}复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
Ⅰ 对于 decltype (expression) var
:
- 第一步:如果 expression 是一个没有用括号括起的标识符,则 var 的类型与该标识符的类型相同,包括 const 等限定符
double x = 5.5;
double y = 7.9;
double &rx = x;
const double * pd;
decltype (x) w; // w is type double
decltype (rx) u = y; // u is type double &
decltype (pd) v; // v is type const double * - 第二步: 如果 expression 是一个函数调用,则 var 的类型与函数的返回类型相同
long indeed (int);
decltype (indeed(3)) m; // m is type int - 第三步:如果 expression 是一个左值,则 var 为指向其类型的引用
double xx = 4.4;
decltype ((xx)) r2 = xx; //r2 is double &
decltype (xx) w = xx; //w is double (Stage 1 match) 第四步:如果前面的条件都不满足,则 var 的类型与 expression 的类型相同
int j = 3; int &k = j; int &n = j; decltype(j + 6) i1; //i1 type int decltype(100L) i2; //i2 type long decltype(k + n) i3; //i3 type int复制代码
- 1
- 2
- 3
- 4
- 5
- 6
Ⅱ
?type? ft(T1 x,T2 y)
Ⅲauto ft(T1 x,T2 y) -> decltype(x + y)