/*第6章 函数*/
**函数是一个命名了的代码块,通过调用函数执行相应的代码
6.1 函数基础
1. 典型函数定义:返回类型、函数名字、形参列表、函数体(复合语句)
**调用运算符(()):作用于表达式,表达式为函数或者指向函数的指针
2. 函数的调用
**用实参初始化函数对应的形参
**将控制权转移到被调函数,主调函数的执行被暂时中断
3. 执行到return语句函数结束执行
**返回return语句中的值
**将控制权转回主调函数
4. 形参和实参
**实参是形参的初始值
**实参和形参有对应关系,类型必须但是没有规定实参的求值顺序
5. 函数形参列表
**可以为空但不能省略(显式或者隐式)
**形参用逗号隔开,每个形参都是含有声明符的声明,对应类型必须显式声明
6. 函数返回类型
**不能是数组类型或者函数类型
**特殊返回类型void,表示不返回任何值
6.1.1 局部对象
**名字的作用域、对象的生命周期
**名字作用域是程序文本一部分
**对象生命周期是程序执行过程中对象存在的时间
**局部变量:形参和函数体内的变量
**会隐藏在外层作用域中同名的其他所有声明
1. 自动对象:只存在于块执行期间的对象
**控制经过变量定义语句时创建对象,到达块末尾销毁对象
**局部变量对应的自动对象
**变量定义本身含有初始值,初始值进行初始化
**变量定义不含初始值,执行默认初始化,而函数体内定义的内置类型变量不被初始化
2. 局部静态对象static
**对象可以存在于局部变量的生命周期贯穿函数调用以及之后的时间
**仅在第一次经过对象定义语句时初始化一次
**内置类型的局部静态变量初始化为0
6.1.2 函数声明(函数原型)描述函数接口
1. 函数只能定义一次,但是可以声明多次
**函数声明无需函数体
**声明不包含函数体,也就无需形参的名字
2. 在头文件中进行函数声明,在源文件中定义
**保证同一函数的所有声明保持一致
**源文件应该包含头文件
6.1.3 分离式编译:按照逻辑关系将程序划分
**编译和链接多个源文件
6.2 参数传递
**形参类型决定和实参交互的方式
**形参是引用类型,将绑定到对应的实参上,否则将实参值拷贝后赋值
**传值调用和传引用调用
6.2.1 传值参数
**初始化非引用类型变量,对变量的改动不会影响初始值
**C++建议使用引用类型的形参替代指针
6.2 2 传引用参数
1. 引用形参绑定初始化它的对象,改变形参也会改变所引用对象(实参)的值
2. 使用引用避免拷贝
**当某种类型不支持拷贝操作,函数只能通过引用访问该类对象
**引用无须改变引用形参值,声明为常量引用
3. 使用引用形参返回额外信息(返回多个值)
6.2.3 const形参和实参
1. 当用实参初始化形参时会忽略顶层const,形参的顶层const会被忽略
**当形参有顶层const,传给它常量或者非常量对象都是可以的
2. 可以使用非常量初始化一个底层const对象,但是反过来
**一个普通的引用必须用同类型的对象初始化
3. 把函数不会改变的形参定义成普通引用是一种比较常见的错误
**尽量使用常量引用
6.2.4 数组形参
**不允许拷贝数组(无法值传递)、使用数组时会将其转换成指针
1. 向函数传递数组
**void print(const int*) //三个数组的唯一形式
**void print(const int[]) //函数作用于一个数组
**void print(const int[10]) //维度表示希望数组有多少元素,但是实际不一定
**三个函数等价
2. 数组以指针形式传递给函数,函数无法知道数组的确切尺寸,应该提供额外信息
**管理指针形参的三种技术:
**使用标记指定数组长度:要求数组本身包含一个结束标记(C风格字符串的空字符)
**使用标准库规范:传递指向数组首元素和尾后元素的指针
**显式传递一个表示数组大小的形参:专门定义一个表示数组大小的形参(C程序和旧式C++常用)
3. 数组形参和const:根据是否需要修改数组内容选用const
4. 数组引用形参
5. 多维数组:
**void print(int (*matrix)[10],int rowsize)
6.2.5 main:处理命令行选项
**命令行选项通过两个(可选)形参传递给main函数:
**int main(int argc, char *argv[]){ }
**第一个形参argc表示数组中字符串的数量,第二个argv数组元素是指向c风格字符串的指针
**int main(int argc, char **argv){ }
**实参传递给main函数,argv第一个元素指向程序名字或者一个空字符串,接下来的元素依次传递命令行提供的实参
**最后一个指针之后的元素值保证为0
**当使用argv中实参时,可选实参从argc[1]开始,argc[0]保存程序名字而非用户输入
6.2.6 含有可变形参的函数
**如果所有实参类型相同,可以传递一个名为initializer的标准库类型
**如果实参的类型不同,可以编写一种特殊的函数-可变参数模板
**C函数接口程序可以使用省略符为形参类型传递可变参数实参
1. initializer_list形参
**函数实参数量未知但是全部实参的类型都相同时可以使用此类型的形参
**用于表示某种特定类型的值的数组,类型定义在同名头文件中
**定义对象时必须说明所含元素类型,且对象中元素永远是常量值
2. 含有initializer_list形参的函数同时可以拥有其他形参
3. ErrCode类用来表示不同类型的错误
4. 省略符形参
**为了便于C++程序访问某些特殊的C代码而设置的,这些代码使用名为varargs的C标准库功能
**通常省略符形参不应用于其他目的
**只能出现在形参列表的最后一个位置,语法形式:
**void foo(parm_list,...);
**void foo(...);
6.3 返回类型和return语句
**return语句终止当前执行函数并将控制权返回调用函数的位置
**语法形式:
**return;
**return expression;
6.3.1 无返回值函数
**只能作用在返回类型是void的函数中,返回void的函数不要求有return,隐式执行return语句
**若void类型函数使用return语句的第二种形式返回其他类型的表达式将出错
**表达式只能是另一个返回void的函数
6.3.2 有返回值函数
**return语句第二形式提供函数的结果
1. 返回类型不是void的函数必须返回对应类型的值或者可以隐式转换成对应类型的类型值
2. 在含有return语句的循环后面也应该有一条return语句,没有则非法,但很多编译器无法发现此类错误
3. 返回值用于初始化调用点的一个临时量,该临时量就是函数调用的结果
4. 不要返回局部对象的引用或者指针
**函数终止意味着局部变量、局部临时量的“引用”和“指针”将不再指向有效的内存地址
5. 函数的返回类型决定函数调用是否是左值,调用一个返回引用的函数可以得到左值,其他返回类型得到右值
**char &get_val(string &str,string::size_type ix)
**{
** return str[ix];
**}
**get_val(s,0) = 'A'; //s[0]值改为'A'
**当返回的对象是需要修改的,那么返回常量的引用无效******
6. C++11 规定函数可以返回花括号包围的值的列表
**函数返回内置类型,则花括号包围的列表最多包含一个值且值所占空间不应大于目标类型的空间
**函数返回类类型,类本身定义初始值如何处理
7. 允许main函数没有return直接结束,没有return语句隐式执行"return 0;"
**main返回值为状态指示器
**为使返回值与机器无关,cstdlib头文件定义两个预处理变量可以分别表示成功和失败:EXIT_FAILURE、EXIT_SUCCESS
8. 递归函数
**函数直接或者间接调用本身的函数
**递归函数中,一定有某条路径是不包含递归调用的!
6.3.3 返回数组指针
**数组不能被拷贝,函数不能返回数组。函数可以返回数组的指针或引用
**定义返回数组的指针或引用的函数比较繁琐,使用类型别名可以简化:
**typedef int arrT[10]; //arrT是一个类型别名
**using arrT = int[10]; //arrT的等价声明
**arrT* func(int i); //func返回一个指向含有10个整数的数组指针
1. 声明一个返回数组指针的函数
**数组的维度必须跟在函数名之后,形参列表也在函数名之后且优先于数组的维度
**语法形式:type(*function(parameter_list))[dimension]
**int (*func(int i))[10];返回一个指向含有10个int整数数组的指针
2. 使用尾置返回类型
**任何函数定义都能使用尾置返回,对返回类型比较复杂的函数最有效
**尾置返回类型跟在形参列表后面并以一个->符号开头
**auto func(int i)->int(*)[10];
3. 使用decltype
**返回指针指向已知数组,使用decltype声明返回类型
**int odd[10]={0};
**decltype(odd) *arrptr(int i); //decltype(odd)只是返回一个数组而不是指向开头的指针
6.4 函数重载
**同一作用域内几个名字相同但是形参列表不同的函数为重载函数,编译器根据传递的实参类型推断选择的函数
1. 定义重载函数
**不允许两个函数除了返回类型外其他所有要素都相同
**重载函数应该在形参数量或者形参类型上有所不同
2. 顶层const不影响传入函数的对象
3. 函数重载适合应用在非常相似的操作,同名但不失去名字中应有的信息
4. const_cast在重载函数的情景中的使用
5. 函数匹配/重载确定
**最佳匹配
**无匹配
**二义性调用:多个非最佳匹配函数
6.4.1 重载与作用域
**遵守作用域的一般性质
6.5 特殊用途语言特性
6.5.1 默认实参
**函数的很多次调用中,形参都被赋予一个相同的值,该值为默认实参
1. 某个形参被赋予了默认值,则它后面所有形参都必须有默认值
2. 调用函数省略实参就可以使用默认实参
3. 合理设置形参顺序,尽量让不使用默认值的形参在前面
4. 给定作用域内 默认实参只能被赋予一次
5. 用作默认实参的名字在函数声明所在其作用域内解析,名字求值过程却发生在函数调用时
6.5.2 内联函数和constexpr函数
1. 规模较小的操作定义为函数的好处:
**可读性比等价的条件表达式强
**使用函数确保行为统一,相同操作保证按同样方式进行
**修改计算过程更方便找到函数
**可以被其他应用重复应用
缺点:
**调用函数一般比等价表达式求值慢一点
**内联函数避免函数调用的开销,将展开实参,在前面求值基础上用类似于表达式的形式求值而非重新入栈
2. 内联函数定义声明:函数返回值前加入inline
3. 内联机制用于优化规模小、流程直接、频繁调用的函数
4. constexpr函数是指能用于常量表达式的函数
**函数返回类型及所有形参的类型都得是字面值类型
**函数体中必须有且只有一条return语句
**constexpr函数被隐式指定为内联函数
**当constexpr函数实参是常量表达式,返回值也为常量表达式,实参不是常量表达式,返回值也不是常量表达式
5. 内联函数和constexpr函数可以多次定义,但多个定义必须完全一致,基于此,通常定义在头文件中
6.5.3 调试帮助
**程序可以包含一些用于调试的代码,但是代码只在开发程序时使用。当程序编写完成并发布时,先屏蔽掉调试代码
**方法用到预处理功能:assert、NDEBUG
1. assert预处理宏
**预处理宏:预处理变量,类似于内联函数。定义在cassert头文件中,和预处理变量一样,程序内名字唯一
**语法形式:assert(expr);
**表达式为假,assert输出信息并终止程序执行,表达式为真,assert什么都不执行
**常用于检查“不能发生”的条件
2. NDEBUG预处理变量
**assert的行为依赖于一个名为NDEBUG的预处理变量的状态
**如果定义了NDEBUG则assert什么也不做,默认状态下没有定义NDEBUG,此时assert将执行运行时检查
**使用#define语句定义NDEBUG关闭调试状态
**assert应该仅用于验证那些确实不可能发生的事情,将assert当成调试程序的一种手段但是不能用它替代真正的运行时逻辑检查和替代程序本身的错误检查
3. NDEBUG编写条件调试代码:如果NDEBUG未定义,将执行#ifndef和#endif之间的代码
**void print(const int ia[],size_t size)
**{
#ifndef NDEBUG
cerr << __func__ << ":array size is " << size << endl; //C++编译器定义__func__局部静态变量用于存放函数名字
#endif
//...
**}
4. 预处理器定义的变量:
**__FILE__:存放文件名的字符串字面值
**__LINE__:存放当前行号的整型字面值
**__TIME__:存放文件编译时间的字符串字面值
**__DATE__:存放文件编译日期的字符串字面值
6.6 函数匹配
1. 选定本次调用对应的重载函数集:候选函数
**同名
**调用点可见
2. 考察实参选出可行函数、实参类型
**形参数量与本次调用实参数量相等
**实参类型与形参类型相同或可转换
3. 选择最匹配函数
**含有多个形参的函数匹配
**该函数每个实参的匹配都不劣于其他可行函数的匹配
**至少有一个实参的匹配优于其他可行函数匹配
6.6.1 实参类型转换
1. 类型转换排序
**精确匹配
**const转换
**类型提升
**算术类型转换或者指针转换
**类类型转换
2. 所有算术类型转换级别一样,不存在优先转换的类型
3. 函数区别于形参是否是常量,则调用编译器根据是否是常量来决定选择
6.7 函数指针
1. 函数指针指向函数而非对象
2. 把函数名作为一个值使用,函数自动转换成指针。可以直接使用函数的指针调用该函数,无需提前解引用指针
3. 指向不同函数类型的指针不存在转换规则:形参、类型必须匹配!!!!
4. 使用重载函数的函数指针,上下文必须清晰界定
5. 不能定义函数类型的形参,但是可以定义指向函数指针的形参,当成指针使用。也可以直接把函数当作实参使用,自动转换成指针
6. 类型别名和decltype简化使用函数指针
7. 返回指向函数的指针
**返回类型不会自动转换成指针,必须显式指定返回类型为指针
/*练习题6.3:编写自己的fact函数,上机检查*/
int main6_3(int num)
{
int sum = 1;
while(num > 1)
sum *= num--;
return sum;
}
/*练习题6_4:编写一个与用户交互的函数,要求用户输入一个数字,计算生成该数字的阶乘*/
int main6_4()
{
int num;
cout << "请输入一个数字" << endl;
while (cin >> num)
{
int sum = 1;
while (num > 1)
sum *= num--;
cout << num << "的阶乘为: " << sum << endl;
cout << "请输入一个数字" << endl;
}
return 0;
}
/*练习题6.5:编写一个函数输出其实参绝对值*/
int main6_5(int num)
{
if(num < 0)
num = -num;
return num;
}
/*练习题6.7:编写一个函数当它第一次调用时返回0,以后每次被调用返回值加1*/
int main6_7()
{
static unsigned i = 0;
return i++;
}
/*练习题6_8:编写一个名为Character_6.h的头文件,令其包含6.1节练的所有函数声明*/
#ifndef __Character_6_H
#define __Character_6_H
int main6_3(int num);
int main6_4();
int main6_5(int num);
int main6_7();
#endif
/*练习题6.10:编写一个函数,使用指针形参交换两整数的值*/
int main6_10(int* p1, int* p2)
{
cout << *p1 << " " << *p2 << endl;
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
cout << *p1 << " " << *p2;
return 0;
}
/*练习题6.11:编写并验证reset函数,使其作用于引用类型的参数*/
int main6_11(int &i)
{
i = 0;
}
/*练习题6.12:编写一个函数,使用引用交换两整数的值*/
int main6_10(int &p1, int &p2)
{
cout << p1 << " " << p2 << endl;
int temp;
temp = p1;
p1 = p2;
p2 = temp;
cout << p1 << " " << p2;
return 0;
}
/*练习题6.17:编写一个函数,判断string对象中是否含有大写字母,编写另一个函数,把对象全部改成小写形式*/
bool main6_17(const string &str)
{
bool flag = false;
for (auto c : str)
{
if (isupper(c))
{
flag = !flag;
return flag;
}
}
return flag;
}
string main6_17(string &str) //因此两个函数有区别
{
for (auto &c : str)
{
c = tolower(c);
}
return str;
}
/*练习题6.21:编写一个函数,令其接受俩个参数:一个是int型,另一个是int指针。函数比较int的值和指针所指的值,返回较大的数*/
int main6_21(const int i1,const int *i2)
{
if(i1 > *i2)
return i1;
else return *i2;
}
/*练习题6.22:编写一个函数,令其交换两个int指针*/
int main6_22(int* i1, int* i2)
{
cout << *i1 << " " << *i2 << endl;
int* temp;
temp = i1;
i1 = i2;
i2 = temp;
cout << *i1 << " " << *i2 << endl;
return 0;
}
/*练习题6_23:编写print函数*/
void main6_23_1(const int* val)
{
cout << "void print(const int* val)" << endl;
cout << *val << endl;
}
void main6_23_2(const int *beg, const int *end)
{
cout << "void print(const int *beg, const int *end)" << endl;
for(;beg!=end;++beg)
cout << *beg << endl;
}
void main6_23_3(const int ia[], size_t size)
{
cout << "void print(const int ia[], size_t size)" << endl;
for (size_t i = 0; i != size; ++i)
cout << *(ia+i) << endl;
}
/*练习题6.25:编写一个main函数,接受两个实参,把实参内容链接成一个string对象并输出出来*/
int main6_25(int argc, char*argv[])
{
for (size_t i = 1; i < argc; ++i)
{
cout << argv[i];
}
cout << endl;
return 0;
}
/*练习题6.26:*/
int main6_26(int argc, char *argv[])
{
for (size_t i = 0; i < argc; ++i)
{
cout << "argv["<< i << "] = \""<< argv[i]<<"\"" << endl;
}
cout << "argv[" << argc << "] = \"" << 0 << "\"" << endl;
system("pause");
return 0;
}
/*练习6.27:编写一个函数,它的参数是initialzer_list<int>类型的对象,函数功能是计算列表中所有元素的和*/
int main6_27(initializer_list<int> ilist)
{
int sum = 0;
for (auto& i : ilist)
{
sum += i;
}
return sum;
}
int main_test_27()
{
vector<int> ivec;
int temp;
while (cin >> temp)
ivec.push_back(temp);
switch (ivec.size())
{
case 5:
cout << main6_27({ ivec[0],ivec[1],ivec[2],ivec[3],ivec[4] });
break;
}
return 0;
}
/*练习6.28:const string &,使用引用避免拷贝过长字符串影响效率*/
/*练习6.29:引用是为了可以直接操作对象,避免繁杂无用的拷贝,对initializer_list对象可以不用,对象元素是
**常量也无法更改。而在元素是字符串等内容,为了避免拷贝可以使用引用*/
/*练习题6.42:给make_plural函数的第二个形参赋予默认实参's',利用新版本的函数输出单词success和failure的单数和复数形式*/
string make_plural(size_t ctr. const string &word = 's', const string &ending)
int main()
{
int i = 0, j[2] = { 0,1 };
//void print(const int* val)
print(&i);
print(&j[0]);
print(&j[1]);
//void print(const int *beg, const int *end)
print(&i, &(i + 1));
print(begin(j), end(j));
//void print(const int ia[], size_t size)
print(&i, 1);
print(j, end(j) - begin(j));
system("pause");
return 0;
}
C++_Primer_Plus学习笔记-第六章
最新推荐文章于 2023-05-05 13:13:21 发布