您的位置:首页 > 编程语言 > C语言/C++

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

2016-09-11 10:25 357 查看
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);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++ Primer读书笔记