函数---C++ 基础

xiaoxiao2021-02-28  67

前言

刚看了《桃子的博客》,关于个人阶段性学习的总结。深感自己要从事后端开发需要学习更多的知识。语言仅仅是基础的基础。还需要对算法以及操作系统、网络、分布式有深入的学习。前方的路还很长,现在必须要先打起精神,先把这个语言过关计划搞定。所有的坚持一定会有收获。

函数是编程语言中的重要一环,实际是一个代码块。有入口和出口。本文只提出一些需要注意的东西。

1.局部对象

函数构成一个新的作用域,在函数内部定义的变量叫局部变量,会隐藏外层作用域同名的声明。

局部变量的生命周期依赖于定义的方式。

自动对象

自动对象是函数中普通的变量,随着函数执行而被创建,随函数退出而被销毁。另外,函数中定义的普通变量如果有初始值,则使用初始值初始化,否则默认初始化,这时候是未定义的的值。类类型默认初始化是自己控制的。

局部静态对象

生命周期和程序生命周期一致,证明局部静态对象的存储位置是静态区。局部静态对象使用static 来定义,函数第一次被执行时初始化。

2.分离式编译

c++支持分离式编译,最好的方式是在头文件中写函数声明,在源文件中写函数定义。需要在源文件中引入头文件。

3.参数传递

传值传引用传指针

具体的概念就不说了,很清晰。提一点,传引用可以避免拷贝,如果函数中不需要改变引用变量,最设置成常量引用。另外,传引用还可以作为一个返回值来使用。因为C++函数只能返回一个值,当然也可以打包成一个类型然后返回,但是这样比较麻烦。

const形参和实参

记住一点,实参初始化是忽略顶层const的,或者说形参的顶层const被忽略掉了。

void fcn(const int i) {} void fcn(int i) {} // 错误:重复定义函数,实际我的编译器是能编译通过的,而且每次调用的都是第一个,这个可能和编译器的实现相关

另外,按引用传递还需要注意引用的初始化,字面值常量需要顶层const。

void fcn(string &s){} fcn("hello"); // 错误:字面值常量,需要const 数组形参

数组形参需要好好复习一下,之前总是出错。

首先,数组有两个特点,1.不允许拷贝数组;2. 使用数组时(通常)会转换成指针。

于是,我们知道数组作为形参,不能值传递,实际传递的是数组的头指针

// 三者都是等价的 void print(const int *); void print(const int[]); void print(const int[10]);

因此,函数体内遍历数组,需要知道数组大小,避免越界。三种方式解决

设置哨兵(字符数组末尾的空字符)传递头指针和尾指针(标准库规范)显示传递一个数组大小的形参

数组引用形参(限制死了形参数组的大小,可用性不好)

void print(int (&arr)[10]) { for (auto elem : arr) cout << elem; } // 这样传递的是数组的引用,维度是类型的一部分 传递多维数组

实际传递的是数组的指针,不能忽略第二维或者第三维的大小。

void print(int (*matrix)[10], int rowsize) {} void print(int matrix[][10], int rowsize) {} // 等价,但是传递的是第一行的数组的指针,还需要传递行数大小 关于值是如何被返回的

函数调用会产生一个临时量,返回值用于初始化这个临时量。

如果返回的是非引用,返回值会被拷贝到调用点。

注意: 不要返回局部对象的引用或指针 如果是返回引用,不要返回字面值,字面值在函数里是一个局部临时量。 返回引用,返回的是左值,要注意是不是常量引用

c++ 11 支持返回初始化列表

返回的列表初始化调用点的临时量。

vector<int> process() { return {1, 2}; }

4.函数重载

函数名字相同,而形参列表不同,称为重载(overload)

因此,注意返回值不同,两个函数的定义还是一样的,这样就会造成编译器错误。

重载与const

顶层const忽略,因此无法区分

void lookup(int i); void lookup(const int i); // 重复声明 void lookup(int *i); void lookup(int *const i); // 重复声明 void lookup(int &i); void lookup(const int &i); // 重载 void lookup(int *i); void lookup(const int *i); // 重载

利用 const_cast 能够显示转换常量和非常量

5.默认实参

一旦设置了默认实参,后面的都需要有默认值默认实参只能设置一次,因此,多次声明要注意不能重复设置实参

6.调试帮助

开发的时候,需要编写某些调试代码,等到发布时需要屏蔽调试代码。这时候需要一些调试方法。

用到两项预处理器功能:assert和NDEBUG。

assert需要头文件 cassert。NDEBUG是一个宏名字,如果定义了NDEBUG,则assert() 啥也不干。否则,assert测试表达式是否为真。

另外,还有很多预处理器阶段进行宏替换的名字

__func__ 当前函数名字__FILE__ 当前文件名__LINE__ 当前行号__TIME__ 文件编译时间__DATE__ 文件编译日期

7.函数指针

函数名作为一个值使用时,该函数自动转换为一个指针。

using F = int (int *, int); using PF = int (*)(int *, int); // 等价于 typedef int F(int *, int); typedef int (*PF)(int *, int); auto f1(int) -> int (*)(int *, int) // 尾指返回类型的方式 decltype(F) *getFcn(int); // decltype根据表达式推测类型,返回的是函数,所以这里还需要加上*

注意: decltype 可以用于推测表达式的类型,如果使用的是函数调用,那推测的是返回类型,而不是函数类型 对一个函数名进行操作,decltype(func) 返回的是函数,并不是函数指针

转载请注明原文地址: https://www.6miu.com/read-46600.html

最新回复(0)