6.1.1
在c++中,名字有作用域,对象有生命周期。
局部变量只在函数的作用域内可见,同时会隐藏外层作用域中同名的其他所有声明。
在所有函数体外定义的对象存在于程序的整个执行过程中。此类对象在程序启动时被创建,知道结束才会被销毁。
局部变量的生命周期依赖于定义的方式。(局部静态变量在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止时才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。局部静态变量的生命周期贯穿函数调用及之后的时间。)
只存在于块执行期间的对象成为自动对象。
形参是一种自动对象。
局部静态变量的生命周期可以贯穿函数调用之后的时间。将局部变量定义成static类型即可。例如:
size_t count_calls()
{
static size_t ctr = 0;
return ++ctr;
}
局部静态变量内置变量初始化为0;
6.1.2
如果一个函数永远不会被用到,那么可以只有声明没有定义。
(函数)声明可以有多个,定义只能有一个,变量不能多次声明。
函数声明也称作函数原型。
建议变量和函数都应该在头文件中进行声明,在源文件中定义。
定义函数的源文件应该把含有函数声明的头文件包含起来,编译器负责验证函数的定义和声明是否匹配。
6.1.3
c++支持分离式编译,允许我们把程序分割到几个文件中去,每个文件独立编译。
6.2
形参初始化的机理和变量初始化一样
6.2.2
copy大的类类型对象或者容器对象比较低效,甚至有的类类型(例如IO)不支持copy操作。
如果函数无须改变引用形参的值,最好将其声明为常量引用。
使用引用形参,可以隐式返回多个值(因为可以直接修改原值)。
6.2.3
用实参初始化形参时,会忽略形参的顶层const。
比如说:
void f(const int a)
{
/*...*/
}
void f(int a)
{
/*...*/
}
这样是会报错的,因为当调用
f(10);
的时候,编译器无法确定调用哪个函数。唯一不同的是,第一个f()不能修改a的值。
可以用非常量初始化一个 底层const对象,但是反过来不行。(只能将底层const 的严格程度提高,不能把底层const 改成底层非const)。
声明的const引用都是底层const。
C++允许用字面值初始化常量引用 ,非常量引用不行。
const int a = 10;
如果函数不改变某个引用形参的值,应该把这个形参设为const引用(反正也不改变值,那就放心传引用吧)。此外,如果不把引用改为const引用的话,会极大的限制函数所能接受的实参类型,比如说不能传入const引用、字面值常量和需要类型转换的对象(代码如下)作为实参。
void reset(const int &a){ } //如果把const去掉,编译就不能通过
int main()
{
short ctr = 0;
reset(ctr);
return 0;
}
因为short转换到int,会产生临时变量(右值),而只有const引用可以引用右值,普通左值不能引用右值。
6.2.4
下面的三个语句意思是一样的
void print(const int*);
void print(cosnt int[]);
void print(const int[10]);//10表示我们期望数组10个元素,实际不一定
可以创建对数组的引用,但你不能创建一个元素都是引用的数组
int a[3] = {1, 2, 3};
int (&b)[3] = a; //此时b包含了a的所有信息
int &b[3] = a; //错误
int &b[3] = {a[0], a[1], a[2]}; //错误
因为[ ] 地优先级比 & 高,如果不加括号,先处理 b[3](说明是数组),再处理 int& (说明是引用)。
6.3.1
有返回值函数有可能出现“ 控制流尚未返回任何值就结束了函数的执行” 这样的错误,在GNU GCC中会警告但不报错。
不要返回局部对象的引用或指针。
一个返回引用的函数,返回的是一个左值,其他返回类型得到右值。
可用返回 vector 来返回多个值。
vector<int> test()
{
/*...*/
return {1, 2, 3};
}
6.3.3
返回数组指针
typedef int arrT[10]; //arrT是一个类型别名,他表示类型是含有10个整数的数组
using arrT = int[10]; //arrT的等价声明
arrT* func(int i); //fun返回一个指向含有10个整数的数组的指针
int arr[10]; //arr是一个含有10个整数的数组
int *p1[10]; //p1是一个含有10个指针的数组
int (*p1)[10] = &arr; //p2是一个指针,它只想含有10个整数的数组
Type (*function(parameter_list)) [dimension]
int (*func(int i)) [10]; //将func(int i) 整体看作一个指针的名字即可func(int i) 表示调func函数时需要int 类型的实参
(*func(inti)) 意味我们可以对函数调用结果执行解引用操作
(*func(int i))[10] 表示解引用func的调用将得到一个大小是10的数组
int (*func(int i)) [10]表示数组中的元素是int类型
相当于
int a[10];
尾置返回类型, (就是定义一个函数时,把返回类型放在尾部。)
auto func(int i) -> int(*) [10] //func接受一个int类型的参数,返回一个指针,该指针指向含有10个整数的数组。
int odd[] = {1, 3, 5, 7, 9};
int even[] = {0, 2, 4, 6, 8};
decltype(odd) *arrPtr(int i)
{
return (i % 2) ? &odd : &even;
}
6.4
函数重载:同一作用域内的几个函数名字相同但形参列表不同。
main()不能重载。
不要乱重载函数,有些实现功能不一样的函数就不要重载。只重载那些功能相近,但是参数类型不同的函数。
6.6
函数匹配:
第一步是确定本次调用对应的重载函数集,集合中的函数成为候选函数。
候选函数的特征 :
1. 与被调用的函数同名。
2.其声明在调用点可见。
第二步考察本次调用提供的实参,然后从候选函数中选出能被这组实参调用的函数,这些新选出的函数成为可行函数。
可行函数的特征:
1. 形参数量与本次调用提供的实参数量相等(若有默认实参,则我们在调用时传入参数数量可少于实际使用的实参数量)。
2. 每个实参的类型与相应的形参类型相同,或者能转换成形参的类型。
第三步是从可行函数中选择与本次调用最匹配的函数。如果有且只有一个函数满足以下条件,则匹配成功。
1.该函数每个实参的匹配都不劣于其他可行函数需要的匹配。
2.至少有一个实参的匹配优于其他可行函数的匹配。
如果检查了所有实参之后没有一个函数脱颖而出,则该调用是错误的。编译器报二义性错误。
6.6.1
最佳匹配的等级
1. 精确匹配,包括以下情况:
①实参类型与形参类型相同。
②实参从数组类型或函数类型转换成对应的指针类型。
③向实参添加顶层const或者从实参中删除顶层const。
2. 通过const 转换实现的匹配。(提高底层const 严格程度)
3. 通过类型提升实现的匹配。
4. 通过算术类型转换或指针转换实现的匹配。
5. 通过类类型转换实现的匹配。
注意:假设有两个函数,一个接受int,另一个接受short,则只有当前调用提供的是short类型的值才会选择short版本的函数。即使实参是一个很小的整数值,也会直接将它提升成int 类型。例如:
void ff(int);
void ff(short);
ff('a'); //char 提升成int, 调用 f(int)
所有算数类型转换的级别都一样,从int向unsign int 的转换并不比从int向double级别高。例如
void manip(long);
void manip(float);
manip(3.14); //错误,二义性。3.14提升为double,既能转化为long,也能转化为float
就存在两种可能的算术类型转换,二义性。
函数匹配和const实参:
Record lookup(Account& );
Record lookup(const Account&);
const Account a;
Account b;
lookup(a); //传入的是const对象,因为不能把普通引用绑定到const对象上,所以匹配第二个函数
lookup(b); //两个都可以,但用非常量对象初始化常量引用需要类型转换,接受非常量形参的版本则与b精确匹配,所以匹配第一个函数。
指针类型也类似;
6.7
bool lengthCompare(const string&, const string&);
bool (*pf)(const string&, const string&) = lengthCompare; //和上一句一样
bool (*pf)(const string&, const string&) = &lengthCompare; //和上一句一样
如果不加括号,则声明了一个接收两个const string引用返回值为bool* 类型的函数,( ()优先级高于*,pf 先与参数列表的括号合并 )
bool* pf(const string&, const string&);
当我们把函数名作为一个值使用时,该函数会自动的转换成指针。
在指向不同函数类型的指针间不存在转换规则。但给函数指针赋一个nullptr 或者 0,可以表示该指针没有志向任何一个函数。
int f1(const string&)
{
/*...*/
}
int f2(const char*)
{
/*...*/
}
int main()
{
int (*pf)(const string&) = f1; //正确
pf = 0; //正确
pf = nullptr; //正确
pf = f2; //错误,不允许任何转换
return 0;
}
可以把函数指针作为函数的形参:
void test(int a, bool pf(const string&));
void test(int a, bool (*pf)(const string&));
上面两个语句意义一样。
可用typedef简化:
typedef bool Func(const string&, const string&);//定义了Func是接受两个const string&实参,返回bool的类型的别名
然后Func就成了一个类型(类似 typedef long long ll; ll a = 123123123L;)
也可用using简化:
using F= int(int*, int); //F是函数类型,不是指针
using PF = int(*)(int*, int); //PF是指针类型
using PF = int* (int*, int); //注意!! 如果不加括号的话,PF就是函数类型,该类型返回int*;
特别注意最后一种情况。
也可用下面的形式直接声明f1
int (*f1(int)) (int*, int);
也可用尾置返回类型:(就是定义一个函数时,把返回类型放在尾部。)
auto func(int i) -> int(*) (int*, int) //func接受一个int类型的参数,返回一个指针,该指针指向含有一个接受int*, int,返回一个int.
string::size_type sumLength(const string&, const string&);
string::size_type largerLength(const string&, const string&);
//根据形参的取值,getFcn函数返回指向sumLength或者largerLength的指针
decltype(sumLength) *getFcn(const string&);
decltype作用于某个函数时,返回的是函数类型,而不是函数指针,所以需要在getFcn前加一个*,显式地表示返回指针。
练习:
6.6
形参是自动对象的一种,存在于程序的整个执行过程中。
静态局部变量在函数体中可见,但生命周期延续到程序结束。
局部变量包含前两者。
void f(int x)
{
int t = x;
static int st;
}
6.10
void swapp(int *a, int *b)
{
int t = *a;
*a = *b;
*b = t;
}
int main()
{
int a = 10, b = 99999;
swapp(&a, &b);
cout << a << " " << b << endl;
return 0;
}
6.18
bool conpare(matrix &m1, matrix &m2);
vector<int>::iterator change_val(int, vector<int>::iterator);