C++ Primer笔记6---chapter6易错点
Posted Ston.V
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ Primer笔记6---chapter6易错点相关的知识,希望对你有一定的参考价值。
1.局部静态变量:在第一次被执行时被初始化,知道程序终止时才被销毁;对于类的static成员变量,其对象的所有实例都共用这一个成员变量;局部静态变量默认初始值为0
//其中该ctr表示自己被调用了多少次 size_t count_calls(){ static size_t ctr=0; return ++ctr; }
2.函数声明没有函数体,所以实际上形参的名字可以省略,当然加上更好
一般习惯声明放在头文件中,定义放在源文件中,含有函数声明的头文件应该被包含到定义函数的源文件中去,使用宏避免重复包含头文件
3.参数传递:传引用调用 & 传值调用
当函数无序改变引用形参的值,最好将其声明为常量引用,此时传递常量或者非常量对象(会忽略掉他的顶层const---所指的对象为常量,如const int a; const int *p;)都可以;但是也因此要注意重复定义的问题,不妨你不修改他时就声明为const就可以了
void fcn(const int i){ //fcn能够读取i,但是不能写i xxx } void fcn(int i){ //错误:重复定义,顶层const会被忽略,所以二者能够传入的参数其实是一样的 xxx }
4.如果调用参数为引用的函数,只能使用为int类型的对象,而不能使用字面值、求值结果为int的表达式、需要转换的对象或者const int类型的对象作为参数
如果调用参数为常量引用的函数,可以使用字面值作为参数
因此我们声明函数的形参尽量使用常量引用,这样就能够避免因为直接用字面值作为实参导致的错误(使用引用的原因是避免了拷贝,节省资源,有的对象甚至不支持拷贝)
5.数组形参的几种写法:
1)使用标记指定数组长度(如字符串的空字符)
2)使用标准库规范
void print(const int *beg,const int * end){ while(beg!=end) cout<<*beg++<<endl; } int j[2]={0,1}; print(begin(j),end(j));
3)显示传递一个表示数组大小的形参(C中最常用),这个对应的实参可以用sizeof表示,也可以用end(j)-begin(j)表示
6.对于二维数组的形参,在C++中二维数组实际上是数组的数组,我们传的应该是数组的第一个元素的地址,所以形参应当是指向数组的指针,注意不要写成指针数组的形式
//其中二维大小不能忽略。一维可以忽略,而且即使形参指定了一维也不影响实参该怎么传怎么传 void print(int (*matrix)[10],int rowSize){ xxx } //等价于 void print(int matrix[][10],int rowSize){ xxx }
7.作为main函数的实参,其中argv[0]保存的是程序的名字,实际的参数要从argv[1]开始
int main(int argc,char *argv[]){ xxx }
8.含有可变形参的函数
1)如果所有实参类型相同,可以传递一个名为initializer_list的标准库类型,这个initializer_list中的对象都是常量值,不可修改
//比如错误处理的例子 void error_msg(ErrorCode e,initializer_list<string> il) { cout<<e.msg()<<endl; for(const auto &elem : il) cout<<elem<<" "; cout<<endl; } //传递参数数目不同,但是类型都必须一致 case 1: error_msg(ErrorCode(1),{"Functionx ","hello","world"});break; case 2: error_msg(ErrorCode(2),{"Functionx ","fuck"});break; xxxxxx
2)如果实参类型不同,可以编写可变参数模板
3)可以使用省略符,来传递可变数量的实参,这种功能一般只用于与C函数交互的接口程序,省略符只能出现在形参列表的最后一个位置,prinf函数实现就是用的这种可变形参
void foo(fmt,...); void foo(...);
原理:利用参数压入栈从右往左的顺序(先入栈的地址更大),可以反推出各个参数的地址以便获取具体的参数值,往往需要有这么一个fmt来表示后面的各个参数的类型,同样的为了利于推算各个参数的地址可以使用标准库中的va_list
具体分析见这位老哥的博客:https://www.cnblogs.com/li-ning/p/9489986.html
9.含有return语句的循环后面也应该有一条return语句,如果没有的话程序就是错误的,很多编译器可能都无法发现此类错误
10.引用返回左值,我们能够给他赋值
char &get_val(string str,string::size_type ix){ return str[ix]; //假定索引有效 } int main(){ string s("hello world"); get_val(s,1)='a'; //返回的引用可以做左值,能够出现在赋值运算左边 return 0; }
11.对于main函数的返回值,在stdlib中有定义EXIT_SUCCESS (0)和EXIT_FAILURE (非0)来用于返回,以表示执行成功或失败,这些都是预处理量。
12.返回数组指针,可以使用typedef来简化写法
typedef int arrT[10]; //arrt是一个类型别名,表示含有10个整数的数组 using arrT = int[10]; //等价写法 arrT *func(int i); //func接受一个整形实参,返回一个包含10个整数的数组
声明一个返回数组指针的函数
//首先区别数组指针和指针数组 int arr[10]; int *p1[10]; //指针数组 int (*p2)[10] = &arr ; //数组指针 //具体的函数返回数组指针的例子,很繁琐 int (*func(int i))[10]; //C++ 11中可以使用尾置返回类型,返回类型写到后面了,前面用auto来表示 auto func(int) -> int(*)[10]; //如果知道函数返回的指针指向哪个数组,可以使用decltype. //decltype(odd)表示的类型是一个指针,这个指针指向的对象与odd一样;但是decltype并不把数组类型转换为对应的指针,所以还要加上一个*(我的理解:decltype返回的类型就是数组,然后就想平时声明指针那样多加个*即可) int odd[] ={1,3,5,7,9}; int even[]={2,4,6,8}; decltype(odd) * arrPtr(int i){ return (i%2)? &odd : &even; }
13.注意:不要返回局部对象的引用或指针,你返回时候人家的生命周期已经结束了
14.函数重载:在同一个作用域内函数名字相同但是形参列表不同;
不允许两个函数除了返回类型外其他所有要素都相同
重载和const形参,会忽略顶层const(自身是个常量),不会忽略底层const(所指的对象是常量)
一个拥有顶层const的形参和没有的顶层const的形参无法区分开,不可重载
//会忽略顶层const(左底右顶),导致重载失败 Record lookup(Phone); Record lookup(const Phone); //重复声明了Record lookup(Phone) Record lookup(Phone *); Record lookup(Phone * const); //重复声明的Record lookup(Phone*);
如果形参是引用或者指针,通过区分其指向的对象是常量还是非常量可以实现函数重载,此时const是底层的
//以下都是ok的 Record lookup(Account &); Record lookup(const Account &); Record lookup(Account *); Record lookup(const Account *);
注意:一个对象本身是常量是顶层const,指针指向的对象是常量是底层const
如果我们参数是一个对象,对于重载他是常量还是非常量是分不清的;但是搞一个指针或者引用指向他,这时候这个对象是常量还是非常量就分的清了(从顶层const变为底层const)
15.重载和作用域:注意在九层作用域声明函数,会隐藏掉外层作用域中的同名函数
string read(); void print(const string &); void print(double); void fooBar(int i){ bool read=false; //错误:上一句中,布尔变量read隐藏了函数read,导致这里read实际上是布尔变量 string s=read(); //强行声明一个新的print函数,会隐藏掉外层的两个print函数声明,下面调用的实际上是这个 void print(int); print("value"); //错误:print(const string &)被隐藏了 print(i); //正确:当前print(int)可见 print(3.15); //正确:调用的print(int),实际执行的是print(3); }
16.默认实参:
1)定义,形如下,有默认值的形参的右侧的形参必须都有形参,使用时如果想要使用默认形参可以直接省略,所以一般把常用默认值的形参放在参数列表靠后,避免形参实参匹配出错
typedef string::size_type sz; string screen(sz ht=24,sz wd=80,char bg=' ');
2)对于声明,多次声明同一个函数合法,但是在给定作用域中想要修改默认实参值的声明不允许,想要给没有默认值的形参添加默认值的声明允许,但是仍要注意有默认实参的形参右侧参数必须都有默认值
3)局部变量不可以作为默认实参,除此之晚,只能类型相符合/能转化的表达式均可。全局变量作默认实参时,若此全局变量的值改变,形参的默认值也会改变
//下面三个的声明必须在函数之外 sz wd=80; char def=' '; sz ht(); string screen(sz=ht(), sz=wd, char =def ); string window=sreen(); //实际调用screen(ht(),80,' '); void f2(){ def='*'; //改变默认实参的值 sz wd=100; //隐藏了外层的wd,但是没有改变默认值 window = screen(); //实际调用screen(ht(),80,'*'); }
17.内联函数&constexpr函数
内联函数:在函数返回值前加上inline声明即可,在编译过程中会将函数展开,从而能够避免函数的调用开销,
constexpr函数:函数的返回值和形参都得是字面值类型,且函数体只能有一条return语句。编译器对constexpr函数的调用替换为其结果值,且被隐式的指定为内联函数
对于给定的内联函数和constexpr函数,他的多个定义必须完全一致,因此他们通常定义在头文件中。
18.调试
可以使assert函数(cassert头文件)和NDEBUG宏搭配来输出调试信息,在发布版本中#define NDEBUG即可让这些assert失效。assert应当只被用于调试。
assert(expr):当expr为0,输出信息并终止程序;当expr为1,啥也不干
一些编译器为我们定义的用于调试的名字:__func__(函数名),__FILE__(文件名), __LINE__(当前行号), __TIME__(文件编译时间), __DATE__(文件编译日期)
19.(重载)函数匹配过程(面试考过):
1)选定本次调用对应的重载函数集,集合中的函数称为候选函数,候选函数的两个特征:一是与被调用函数名相同,二是其声明在调用点可见
2)考察本次调用的实参,然后从候选函数中选出能够这组实参调用的函数,这些函数称为可行函数,可行函数的两个特征:一是形参数目与实参数目相同(如果函数有默认实参,则传入实参可能比实际实参少),二是每个实参类型与形参类型相同或者能实参转化为形参类型
3)从可行函数中找出与本次调用最匹配的函数,逐一检查实参,实参类型与形参类型越接近,匹配程度越高
4)最终根据3)选出唯一一个函数:该函数每个实参的匹配都不劣于其他可行函数需要的匹配,且至少有一个实参的匹配优于其他可行函数提供的匹配。
5)若1)2)没有符合要求多的函数,则报告无匹配函数的错误;若4)中没能确定唯一的函数,则因为调用具有二义性二拒绝其请求。
20.函数指针:
//1.原始的定义 bool (*pf)(int a,int b); bool func(int a,int b); pf=func; //pf指向func pf=&func //等价,&符号可以省略 //2.重载函数指针 void ff(int *); void ff(unsigned int); void (*pf1)(unsigned int)=ff //pf1指向ff(unsigned) void (*pf2)(int)=ff //错误:没有一个形参匹配的 double (*pf3)(int *)=ff //错误:返回类型不匹配 //3.函数指针可以做形参,也可以做返回值,可以使用别名来简化 //3.1作形参 void test(func,int x); //函数类型会自动转化为函数指针类型,作为实参也会自动转化为指针类型 void test(&func,int x); //同上,等价 //使用别名 typedef bool(*pf4)(int a,int b); typedef decltype(func) *pf4 //同上,等价 void test(pf4,int x) //3.2作返回值 using F =bool(int ,int); //函数类型 using PF=bool(*)(int ,int); //函数指针类型!!!!!!!!!!!!!!!一定要注意好这个类型 PF f1(int); //f1返回函数指针类型 F f1(int); //错误:函数类型不能作为返回值类型 F* f1(int); //正确,等价 //直接声明 & 使用尾置返回类型的方式 int (*f1(int))(int ,int); auto f1(int) -> int(*)(int ,int);
以上是关于C++ Primer笔记6---chapter6易错点的主要内容,如果未能解决你的问题,请参考以下文章
C++ Primer笔记16---chapter13 代码实例
C++ Primer笔记16---chapter13 代码实例
C++ Primer笔记15---chapter13 拷贝控制2