C++ Primer阅读心得(第六章)

1. C++语言使用调用操作符(一对括号)实现函数调用。调用操作符的操作数是函数名和参数列表,返回值是函数的返回值。(醍醐灌顶啊~~)

2. 函数调用时的行为:

  • 实参入栈:默认行为是从右到左入栈
  • 现场保护:返回地址、当前函数的栈基址、各种寄存器入栈
  • 执行函数
  • 恢复现场:各种寄存器出栈(恢复寄存器)、栈基址出栈(恢复栈基址)、返回地址出栈,以及各种清理工作
  • 在返回地址继续执行原来的函数

3. 形参的名字是可选的。所以在函数声明时可以一个形参名都不给,在函数定义时,函数体中不使用的参数也可以不起名。但是注意在调用的时候,即使形参没有名字也必须给它个实参,否则调用失败。

void func1(int, int, int); //ok,没名字也可以
void func1(int a, int b, int) //ok,不使用就没问题
{
    cout<<a<<b<<endl;
}
func1(1,2,3); //必须传入3个实参,不能因为第三个没有名字就不传

4. 自动对象、局部对象和局部静态对象:

  • 自动对象:只存在于语句块执行期间的对象被称为自动对象。由于自动对象出了块之后会被自动销毁,所以千万不要返回自动对象的指针或者引用。
  • 局部对象:函数形参和函数体内部定义的对象统称为局部对象。非静态的局部对象都是自动对象。
  • 静态局部对象:局部对象前加static就得到了静态局部对象。静态局部对象在程序第一次运行过它的定义时创建,直到程序退出时才被销毁,包含它定义的函数退出也不会对它造成影响。静态局部变量会被默认初始化为0.
string& func(int a) //a是局部对象也是自动对象
{
    string b = "abc"; //b是局部对象也是自动对象
    static int cnt = 0; //静态局部对象,非自动对象
    cnt++;                 //函数每被调用一次,就累加一次,不受函数退出的影响
    return b;             //错误,b已被销毁
}

5.传值、传引用、传常量引用

参数行为接收范围
可修改,但不影响原值拷贝对象,表达式,常量值/表达式
引用可修改,影响原值传入地址,节省空间对象
常量引用不可修改传入地址,节省空间对象,表达式,常量值/表达式

所以在参数不被修改的情况下,对于Class和Struct的对象(除STL迭代器和函数对象之外)尽量使用const引用形参,因为它可以接受更大的传入范围,同时与其他库函数配合的更好。此外,由于常量引用实际是传递一个32位/64位的地址,相对于内置类型(char)占用空间更多,因此对于内置类型传递值更高效。

6. 当我们为函数传入一个数组时,实际上传入的是数组首元素的指针。

void func(char a[]);
void func(char *a);  //与上面的等价

当我们传入多维数组时,编译器同样会把它转化为数组首元素的指针,也就是转化成一个指向固定大小数组的指针。所以数组的第二维以及后面的数字都不能省略,否则无法通过编译。

void func(int matrix[][10]); 
void func(int (*matrix)[10]); //与上面的等价
void func(int matrix[][]); //错误,不能省略第二维

7. 含有可变长度参数的函数:

  • C++11风格:利用列表初始化和新增的initializer_list类型(对比vector,成员都为常量并且本身也无法被修改(根本没有修改的成员函数...))实现可变参,唯一需要注意的是initializer_list是个模板,它要求传入的数据都为同一类型。注意:必须使用列表的形式向initializer_list传递值。
    void func(int i, initializer_list<int> il){...}
    func(0,{1,2,3,4,5}); //ok,编译器使用{}和它里面的东西初始化initializer_list
  • C语言风格:int function(int para, ...)

8. 如果函数返回指针、引用或者类对象,我们可以在它的调用后面直接跟上点操作符或者箭头操作符;如果函数返回的是引用,那么函数调用可以作为左值放在复制表达式的左边。

string &GetString(vector<string> &buffer, size_t pos)
{
    return buffer[pos];
}
auto sz = GetString(buffer, pos).size(); //ok,左结合
GetString(buffer, pos) = “replacement”; //ok,返回的引用可以作为左值

9. 因为c++11中新增了列表初始化,所以可以在函数中return 一个{}包围起来的列表,只要它能够初始化返回值即可。

vector<string> func()
{
    return {"This","is","a","test."};
}

10. 在cstdlib中定义了两个main函数的返回值:EXIT_SUCCESS和EXIT_FAILURE,它们两个是预处理变量(也就是#define出来的),与机器无关,能更好的保障跨平台编译。(话说:我真没用过,知错能改,马上使用~~)

11. 返回数组指针的函数:

int (*p) [10]; //指向大小为10的int型数组的指针
int (*func(int i)) []; //返回数组指针的函数

又一个必须从中间向两边读取的奇葩...在c++11中,提供了两种方式来简化这个定义,让人类更加容易读懂:

  • 尾置返回类型:(清晰多了~~)
    auto func(int i) -> int *[10]; //把返回类型放在最后,用一个->指向它,在返回类型本该出现的地方放一个auto
  • 使用decltype简化:(临时typedef的感觉?)注意:decltype一个数组会得到一个数组类型,函数无法返回数组类型,所以必须在后面加个*,变成对应的指针类型
    int a[10];
    decltype(a) *func(int i); //注意加*

12. 尾递归:在函数的return语句中调用函数(函数不能在表达式中,单独的调用)被称为尾调用,如果调用的是自身,则称为尾递归。有些编译器支持尾递归,函数使用尾递归时调用函数不入栈,而是位置直接被被调用函数覆盖,所以即使递归层数很深也不会引起栈溢出。

13. 重载的函数在程序员看来是同一个函数名,但是编译之后其实是两个名字。例:int test(int a, int b)和float test(float a, float b),编译之后函数名可能是_int_int_test和_float_float_test。

14.main函数不能重载(汗!)

15.仅仅返回值类型不同的两个函数不构成重载,非引用/指针形参与const非引用/指针形参不构成重载,例:

int func(int)
bool func(int)
//不构成重载,只有返回值类型不同

int func(int)
int func(const int)
//不构成重载,int和const int对于func来说效果一样

int func(int&)
int func(const int&)
//构成重载,int& 与 const int&是不同的类型

16. 重载函数应该在同一个作用域中声明,否则会造成屏蔽而不是重载。

17.确定重载函数的步骤:

1).寻找与调用同名的函数,作为候选函数;
2).选择具有与实参个数相同形参的,类型匹配或者可转换的函数,作为可行函数;
3).寻找最佳匹配(a精确匹配,bconst转换,c类型提升(无精度损失),d类型转换(有精度损失),e类类型转换(单参构造函数))
4).找到多个最佳匹配即产生二义性,报错。

18. 默认实参:如果一个形参具有默认实参,那么它后面的所有形参都必须具有默认形参,具有默认实参的函数最好定义在头文件中以减少冲突。注意:不能使用局部变量作为默认实参;该函数在调用时才会计算默认实参的值。(也就是说,在调用前修改作为默认实参的全局变量,会修改调用时默认的值)。

extern int i;
void func(int j = i){...} //ok,全局变量可以作为默认实参

void dosth()
{
    i = 10;
    func();    //默认参数现在变为10
}

19. inline(内联)函数会在被调用点上展开它的代码,这个展开通常在编译期实现,所以它应当被定义在头文件中以帮助编译器更快的找到并减少冲突;另外,在类的声明中定义的函数被默认为内联函数。不要滥用inline,因为这会导致代码规模急剧的膨胀。注意,只有在开启编译器优化的时候才有inline的行为(g++ -O0就没有,而-O2就有,并且用启发式算法决定函数是否值得inline,同时考虑文件大小,而-O3则不考虑文件大小。)#pragma auto_inline(on/off)只有在编译器开启inline的情况之下才会有作用。

20. constexpr函数:在C++11中,可以使用关键字 constexpr修饰函数,允许用户保证这个函数是编译期常数。constexpr函数要求:其返回值和形参都是字面值(返回值不能是void);函数体中只能包含一个return语句(不是只能返回一个常量,也可以返回一个编译期可知的表达式)。当使用constexpr函数时,编译器会负责检查它是否符合要求,如果你传入一个局部变量,那么编译器会报错。constexpr函数默认是inline的,所以最好也定义在头文件中。

const int c = 2;
constexpr int get_c(int i){return i*c;} //ok
constexpr int get_cc(int i){return i*get_c(i);} //ok

21. assert:assert是个预处理的宏,接受一个表达式,如果表达式为假,则打印信息并终止程序的运行。(天~~我一直以为是个函数呢....)

22. NDEBUG:#define了NDEBUG之后assert就不再工作了,我们也可以编写类似的语句来让NDEBUG统一阻止调试语句的执行。预处理器定义了_ _func_ _、_ _FILE_ _、_ _LINE_ _、_ _TIME_ _和_ _DATE_ _几个const char*类型的名字,以帮助我们打印调试信息。(这个好用啊~~)

23.函数指针:

//声明
bool (*pf) (int, int);

//初始化
bool comp(int a, int b){...}
pf = comp;
int a, b;
(*pf)(a, b);

//数组
bool (*pf[10]) (int, int);

//做参数
int func(int, int, bool(int, int));

//做返回值
bool (*func(int, int)) (int, int)

//使用typedef能简化
typedef bool(*cmpfnc) (int, int)
cmpfnc pf;
int func(int, int, cmpfnc)
cmpfnc func(int, int)
 
//使用decltype简化,注意decltype返回的是函数类型,需要加个*才是指针
decltype(comp) *func(int,int)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《C Primer》是一本针对C语言初学者的经典教材,第五版的第六章主要介绍了函数的基本概念和用法。 在第六章中,教材首先介绍了函数的定义和调用。函数是具有独立功能的代码块,可以通过函数名来调用。函数由函数头、参数、函数体和返回值组成。函数头包括函数类型、函数名和参数类型,参数用于接收传递给函数的值,返回值用于将结果返回给调用者。 接着,教材详细介绍了函数的参数传递方式,包括按值传递、按引用传递和按指针传递。按值传递是指将参数的值复制给形参,按引用传递是指将参数的引用传递给形参,而按指针传递是将参数的地址传递给形参。不同的传递方式在函数内部对参数进行操作时,会对应不同的结果。 此外,教材还讲解了函数的返回值和函数的调用顺序。函数的返回值类型由函数头中的类型确定,可以是任意类型。当函数需要返回多个值时,可以使用结构体或指针进行返回。函数的调用顺序决定了函数执行的顺序,即哪个函数先执行,哪个函数后执行。 在函数的实现过程中,教材介绍了函数的定义和声明、局部变量和全局变量、递归等内容。通过这些知识点的学习,读者可以了解到函数的具体细节和一些实践技巧。 总的来说,第五版的第六章通过清晰的语言和丰富的例子,循序渐进地讲解了函数的基本概念和用法。学完这一章,读者将能够理解函数的定义、调用、参数传递、返回值和实现等方面的知识,为之后的学习和实践打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值