函数基础:
局部对象:
形参和函数体内部定义的变量统称为局部对象。它们对函数而言是“局部”的,仅在函数的作用域可见,同时局部变量还会隐藏在外层作用域中同名的其他所有声明中。
自动对象:生命周期从变量声明开始,到函数块末尾结束
局部静态对象:生命周期从变量声明开始,到程序结束才销毁
size_t 类型表示C中任何对象所能达到的最大长度,它是无符号整数。它是为了方便系统之间的移植而定义的,不同的系统上,定义size_t 可能不一样。size_t在32位系统上定义为 unsigned int,也就是32位无符号整型。在64位系统上定义为 unsigned long ,也就是64位无符号整形。size_t 的目的是提供一种可移植的方法来声明与系统中可寻址的内存区域一致的长度。因为size_t 是无符号的,一定要给这种类型的变量赋正数 。
控制流第一次经过ctr的定义之前,ctr被创建并初始化为0.每次调用ctr加1并返回新值。每次执行count_calls函数时,变量ctr的值都已经存在并且等于函数上一次退出时ctr的值。因此,第二次调用时ctr的值是1,第三次调用时ctr的值是2,以此类推。
参数传递:
传值参数:形参不改变实参
这段程序的理解:
形参不改变实参。reset(&i)调用函数相当于int *ip=&i。所以函数第一行的*ip=0其实就是改变了i的值,而第二行的ip=0只是改变了形参的值,对i并没有影响。
传引用参数:
引用就是贴标签,相当于给j贴上i的标签,i改变了,j也会改变。
使用引用避免拷贝:
使用引用形参获取额外的信息:
string::size_type它是一个无符号类型的值,而且能够存放下任何string对象的大小。另外,string类型的索引也是一个size_type类型的。下标运算符 [ ] 接收的就是string::size_type类型。
当我们的程序需要在不同机器或者运行环境中使用的时候,定义为int就不太好,因为对应机器中的int可能较小,出现溢出。另外,如果我们的string特别长,远超int所能表示的范围,那么也是存在问题的。而size_type是一个设计为可以存放任何大小string对象的类型。
还有,应该尽可能避免无符号类型和int型数的比较。如果n是一个具有负值的int型,那么表达式 str.size()<n 的判断将坑定是 true。因为负数n,会自动转换为一个特别大的无符号类型的值。
一个函数只能返回一个值,但有时候函数需要返回多个值,这时我们可以返回一个数组或vector, 当然如果返回的值的数量比较少,也可以采用引用形参的方法。
使用引用形参时,尽管函数还是只返回一个值,但引用传值导致被引用的实参也发生了改变。将我们想要获得的值作为被引用传值的实参,这样就可以获得额外信息。
指针或引用形参与const:
代码的理解:
第一行调用代码上面解释过,i的值会变成0;
第二行代码是const类型的实参,不能对其进行修改;
第三行是传引用参数,调用第二个函数;
第四行不能对const类型贴标签进行引用;
数组形参:
int i = 0, j[2] = {0,1};
print(&i);//正确:&i的类型是int*
print(j);//正确:j转换成int*并指向j[0]
数组作为参数传递时,实际上传递了一个指向数组的指针,因为数组会被转换成指针,所以为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
以数组为形参的函数必须保证使用数组时不越界,有以下几种方法:
这种方法适用于有明显结束标记且该标记不会与普通数据混淆的情况,但是对于像 int 这样所取值都是合法值的数据就不太有效了。
传入两个指针:一个指向要输出的首元素,另一个指向尾元素的下一位置:
传递给函数的 size 值不超过数组实际大小,函数就是安全的。
将变量定义为数组的引用:
因为数组的大小是构成数组类型的一部分,所以只要不超过维度,在函数体内就可以放心地使用数组。但是,这一用法无形中限制了 print 函数的可用性,只能将函数作用于大小为10的数组。
传递多维数组:
和所有的数组一样,当将多维数组传递给函数时,真正传递的是指向数组首元素的指针。因为我们处理的是数组的数组,所以数组首元素本身就是一个数组,指针就是一个指向数组的指针。
数组的第二维的大小都是数组类型的一部分,不能省略:
//matrix指向数组的首元素,该数组的元素由10个整数构成的数组 void print(int(*matrix)[10], int rowSize) {/*...*/}
上述语句将matrix声明成指向含有10个整数的数组的指针。
*matrix两端的括号必不可少: int *matrix[10]; //10个指针构成的数组 int (*matrix)[10]; //指向含有10个整数的数组的指针
//等价定义 void print(int matrix[][10], int rowSize) {}
matrix 的声明看起来是一个二维数组,实际上形参是指向含有10个整数的数组的指针。
返回类型与return:
无返回值函数:
有返回值函数:
不要返回局部对象的引用或指针:
当函数执行完毕时,将释放分配给局部对象的存储空间。此时,对局部对象的引用就会指向不确定的内存。
函数的返回类型可以是大多数类型。特别地,函数也可以返回指针类型。和返回局部对象的引用一样, 返回指向局部对象的指针也是错误的。 一旦函数结束,局部对象被释放, 返回的指针就变成了指向不再存在的对象的悬垂指针。
列表初始化返回值:
在C++11之前,如果我们想要返回一组数据,我们必须在子函数中构造一个对应的容器,借助容器来进行返回。
vector<int> process()
{
vector<int> v={1,2,3,4}
return v;
}
在新标准下,我们可以直接返回字面值,该字面值会用于容器的构造,而无需我们自己去构造。
vector<int> process()
{
return {1,2,3,4};
}
返回数组指针:
数组不能拷贝,所以函数不能直接返回数组。
(1)声明一个返回数组指针的函数
这和下面的定义比较像:
int arr [10]; //arr是一个含有10个整数的数组
int *p1[10]; //p1是一个含有10个指针的数组
int (*p2) [10]=&arr; //p2是一个指针,它指向含有10个整数的数组
(2)使用尾值返回类型
函数重载:
函数名称相同但是形参列表不同。
重载与const形参:
//对于接受引用或指针的函数,对象是常量还是非常量对应的形参不同
//定义了4个独立的重载函数
Record lookup(Account&);//函数作用于Account的引用
Record lookup(const Account&);//新函数,作用于常量引用
Record lookup(Account*);//新函数,作用于指向Account的指针
Record lookup(const Account*);//新函数,作用于指向常量的指针
- 因为 const 不能转换成其他类型,所以只能把 const 对象传递给 const 形参。
- 因为非常量可以转换成 const ,所以上面的4个函数都能作用于非常量对象或指向非常量对象的指针。
- 传递一个非常量对象或者指向非常量对象的指针时,编译器会优先选用非常量版本的函数。
重载与作用域:
string read();
void print(const string &);
void print(double);//重载print函数
void fooBar(int ival)
{
bool read = false;//新作用域:隐藏了外层的read
string s = read();//错误:read是一个布尔值,而非函数
//不好的习惯:通常来说,在局部作用域中声明函数不是一个好的选择
void print(int);//新作用域:隐藏了之前的print
print("Value:");//错误:void print(const string &)被隐藏掉了
print(ival);//正确:当前print(int)可见
print(3.14);//正确:调用print(int);print(double)被隐藏掉了
}
- 编译器调用 read 的请求时,找到的是定义在局部作用域中的 read 。这个名字是布尔变量,语句非法。
- 在 fooBar 内声明的 print(int) 隐藏了之前两个 print 函数,因此只有一个 print 函数可用:该函数以 int 值为参数。
- 一旦在当前作用域中找到所需要的名字,编译器就会忽略外层作用域中的同名实体。
- 当 print 传入一个 double 类型的值时,编译器在当前作用域内发现了 print(int) 函数, double 类型的实参转换成 int 类型,因此调用是合法的。
C++中,名字查找发生在类型检查之前。
void print(const string &);
void print(double);//print函数的重载形式
void print(int);//print函数的另一种重载形式
void fooBar2(int ival)
{
print("Value:");//调用print(const string &)
print(ival);//调用print(int)
print(3.14);//调用print(double)
}
特殊用途语言特性:
默认实参:
函数调用时实参按其位置解析,默认实参负责填补函数调用缺少的尾部实参(靠右侧位置)。要想覆盖 backgrnd 的默认值,必须为 ht 和 wid 提供实参。
- ? 是个 char ,而函数最左侧形参的类型 string::size_type 是一种无符号整数类型,所以 char 类型可以转换成函数最左侧形参的类型。
- 当调用发生时,char 类型的实参隐式地转换成 string::size_type ,然后作为 hight 的值传递给函数。
- ? 对应的十六进制数是 0x3F,也就是十进制数的 63,所以该调用把值 63 传给了形参 height
内联函数:
在每个调用点上“内联地”展开,避免函数调用的开销。
一般来说,内联机制用于优化规模小、流程直接、调用频繁的函数。
constexpr函数是指能用于常量表达式的函数。函数的返回类型及所有形参的类型都是得是字面值类型,而且函数体中必须有且只有一条 return 语句(1,2行):
因为编译器能在程序编译时验证 new_sz 函数返回的是常量表达式,所以可以用 new_sz 函数初始化 constexpr 类型的变量 foo。
执行初始化任务时,编译器把对 constexpr 函数的调用替换成其结果值。为了能在编译过程中随时展开,constexpr 函数被隐式地指定为内联函数。
constexpr 函数体内也可以包含其他语句,只要这些语句在运行时不执行任何操作就行。constexpr 函数中可以有空语句、类型别名以及 using 声明。
函数匹配:
第一行f(5.6):寻找最佳匹配,找到第二行,虽说可以,但是需要类型转换,即double->int。
第二行f(42,2.56):寻找最佳匹配,找到第三行(int匹配,但是后面的需要转换),找到第四行(double需要转换,第二个匹配)。因为两个都不是特别匹配,所以会产生二义性。
对于manip的函数,3.14是double型的,double到long,double到float都是属于算术类型转换,会造成二义性。
函数指针:
函数指针的类型由它的返回值和形参类型共同决定,与函数名无关:
pf 是一个指向函数的指针,其中该函数的参数是两个 const string 的引用,返回值是 bool 类型。
不写括号 pf 是一个返回值为 bool 指针的函数:
重载函数的指针:
使用重载函数时,上下文必须清晰地界定到底应该选用哪个函数。
void ff(int*);
void ff(unsigned int);
void (*pf1)(unsigned int) = ff;//pf1指向ff(unsigned int)
编译器通过指针类型决定选用哪个函数,指针类型必须与重载函数中的某一精确匹配:
void (*pf2)(int) = ff;//错误:没有任何一个ff与该形参列表匹配
double (*pf3)(int*) = ff;//错误:ff和pf3的返回类型不匹配
函数指针形参:
形参可以是指向函数的指针,形参看起来是函数类型,实际上却是当成指针使用:
//第三个形参是函数类型,它会自动地转换成指向函数的指针
void useBigger(const string& s1, const string& s2, bool pf(const string&, const string&));
//等价声明:显式地将形参定义成指向函数的指针
void useBigger(const string& s1, const string& s2, bool (*pf)(const string&, const string&));
可以直接把函数作为实参使用,此时它会自动转换成指针:
//自动将函数 lengthCompare 转换成指向该函数的指针
useBigger(s1, s2, lengthCompare);
直接使用函数指针类型显得冗长而烦琐,类型别名和 decltype 能简化使用函数指针的代码:
//Func 和 Func2是函数类型
typedef bool Func(const string&, const string&);
typedef decltype(lengthCompare) FuncP2;//等价的类型
//FuncP和FuncP2是指向函数的指针
typedef bool(*FuncP)(const string&, const string&);
typedef decltype (lengthCompare)* FuncP2;//等价类型
Func 和 Func2 是函数类型,FuncP 和 FuncP2是指针类型。
decltype 返回函数类型,此时不会将函数类型自动转换成指针类型,只有在结果前面加上 * 才能得到指针。
重新声明 useBigger :
//useBigger 的等价声明,其中使用了类型别名
void useBigger(const string&, const string&, Func);
void useBigger(const string&, const string&, FuncP2);
两个声明语句声明的是同一个函数,第一条语句编译器自动地将 Func 表示的函数类型转换成指针。
返回指向函数的指针:
必须把返回类型写成指针形式,编译器不会自动地将函数返回类型当成对应的指针处理。
使用类型别名声明一个返回函数指针的函数:
using F=int(int*, int);//F是函数,不是指针
using PF=int(*)(int*, int);//PF是指针类型
使用类型别名将 F 定义成函数类型,将 PF 定义成指向函数类型的指针:
PF f1(int);//正确:PF是指向函数的指针,f1返回指向函数的指针
F f1(int);//错误:F是函数类型,f1不能返回一个函数
F *f1(int);//正确:显式地指向返回类型是指向函数的指针
也能用下面形式直接声明 f1 :
int (*f1(int))(int*, int);
由内向外顺序阅读:f1 有形参列表,所以 f1 是个函数。 f1 前面有 * ,所以 f1 返回一个指针。指针的类型本身也包含形参列表,因此指针指向函数,该函数的返回类型是 int 。
使用尾置返回类型的方式声明一个返回函数指针的函数:
auto f1(int) -> int (*)(int*, int);
抽象数据类型:
类作用域和成员函数:
成员体可以随意使用类中的其他成员而无需在意这些成员出现的相应次序。
构造函数:
构造函数和类同名,没有返回值,它的任务是初始化类对象的数据成员。
类可以包括多个构造函数,和重载函数差不多。
构造函数不能被声明为const。
拷贝、赋值和析构:
访问控制与封装:
使用访问说明符加强类的封装性:
public:类的接口,在整个程序内可以被访问。
private:封装类的实现细节。
class和struct定义类唯一的区别就是默认的访问权限不同,class默认的是private,而struct默认的是public。
友元:
允许其他类或函数访问自己的非公有成员。