《C++ Primer》读书笔记——第六章

来源:互联网 发布:linux autofs 编辑:程序博客网 时间:2024/05/29 11:37

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个整数的数组。


使用decltype

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.


可以用decitype简化:

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);





1 0
原创粉丝点击