C++Primer 第6章 函数

时间 : 2023-05-22 11:24:16 来源 : 哔哩哔哩

第6章 函数

函数是一个命名了的代码块,我们通过调用函数执行相应的代码。函数有0个或多个参数,而且(通常)会产生一个结果。可以重载函数,也就是说,同一个名字可以对应几个不同的函数。

6.1 函数基础

函数最外层作用域中的局部变量不能和函数形参重名。p184函数的返回类型不能是数组类型和函数类型,但可以是指向数组或函数的指针。p184

6.1.1 局部对象

局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数执行结束也被会对它有影响,它只会被初始化一次。p185如果局部静态对象没有显示初始值,它将执行值初始化。p185

6.1.2 函数声明

函数定义的源文件应把函数声明的头文件包含进来,编译器负责验证函数定义和声明是否匹配。p186


(相关资料图)

6.1.3 分离式编译

分离式编译将程序按照逻辑关系划分开来。p186如果我们修改了其中一个源文件,那么只需要重新编译改动了的文件。p187

6.2 参数传递

形参初始化的机理和变量初始化一样。p187

6.2.1 传值参数

指针形参拷贝的是指针的值,两个指针是不同的指针,但指向相同的对象。p188

6.2.2 传引用参数

拷贝大的类类型对象或容器对象比较低效,有些类类型根本不支持拷贝操作,函数可通过引用形参访问该类型对象。p189

6.2.3 const形参和实参

顶层const可以表示任意对象是常量,而底层const与引用和指针等符合类型的基本类型部分有关。p57

1int x = 0;2const int *const p = &x //左边是底层const右边是顶层const3const int &r = x //声明引用的都是底层const在执行对象的拷贝操作时顶层const不受什么影响。p58对于底层const的对象必须固有相同的底层const资格,或者两个对象的数据类型必须能够相互转换。p58可以用非常量去初始化一个底层const对象,但不能用底层const对象去初始化一个非常量。p191将不会改变的形参定义为常量引用,扩大了函数所能接受实参类型。p192

6.2.4 数组形参

因为不能拷贝数组,以及数组名会被转换成指针,所以当函数传递一个数组时,实际上传递的是指向数组首元素的指针。p193

1//这三个print是等价的,调用时只检查参数是否是const int*, 所以传入的是一个int的指针也正确2void print(const int*);3void print(const int[]);4void print(const int[10]); //10表示期望数组元素个数,与实际传入数组的元素个数无关5获得数组尺寸的三种常用技术:p193使用标记指定数组长度,如c风格字符串。使用标准库规范,传入数组首指针和尾后指针。

1void print(const int *beg, const int *end)2{3    while(beg != end)4    {5        /*...*/6    }7}显示传递一个表示数组大小的形参。当函数不需要对数组元素修改时,数组形参应该是指向const的指针。p194数组本身是对象所以允许定义数组的指针以及数组的引用。p102

1int *ptrs[10]; //包含10个指针的数组2int &ref[10]; //错误,不存在引用的数组3int (*Parray)[10] = &arr; //指向一个含有10个整数数组的指针,&不能少4int (&arrRef)[10] = arr; //引用一个含有10个整数数组的引用, 此时的数组名不再是一个指针多维数组是数组的数组,多维数组的首元素本身就是一个数组,所以多维数组传递的是一个指向数组的指针。数组第二维(以及后面所有维度)的大小都是数组类型的一部分,不能省略。

1void print(int (*matrix)[10], int rowSsize); //matrix指向数组的首元素,一个10个整数构成的数组2void print(int matrix[][10], int rowSize); //等价定义,编译器同样会忽略掉第一个维度

6.2.6 含有可变形参的函数

如果所有的实参类型相同,可以转换成一个名为initializer_list的标准库类型。p197

拷贝或者赋值一个initializer_list对象不会拷贝列表中的元素;拷贝后,原始列表与副本共享元素。p198

//验证方法:initializer_list<int> l1 = { 1, 2, 3, 4 };initializer_list<int> l2(l1);cout << l1.begin() << endl;cout << l2.begin() << endl; //结果输出地址相同

initializer_list对象中的元素永远是常量值,我们无法改变其中元素的值。p198

1int sum(initializer_list<int> li)2{3    int res = 0;4    for(const int &i : li) //引用防止拷贝,但必须加const或者直接用auto&5    {6        res += i;7    }8    return res;9}

1sum({1, 2, 3, 4}); //调用

6.3 返回类型和return语句

6.3.2 有返回值的函数

返回一个值的方式和初始化一个变量或形参的方式完全一样:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。p201不要返回局部对象的引用或指针。p201调用运算符的优先级与点运算符和箭头运算符相同,符合做结合律,因此,可以用函数调用的结果访问结果对象的成员。p202

1shorterString("hi", "bye").size();调用一个返回类型引用的函数得到左值,其他返回类型得到右值,可以为返回类型是==非常量==引用的函数的结果赋值。p202

1int& func(int& x) 2{ 3    return x; 4} 5 6int main() 7{ 8    int x = 10; 9    func(x) = 20;10    cout << x << endl; //x被修改为了201112    system("pause");13}函数可以返回花括号包围的值的列表。p203

6.3.3 返回数组指针

因为数组不能拷贝,所以只能返回数组的指针或者引用,有四种方法:

直接定义

1int (&func(int i))[10];利用类型别名

1//两种声明等价2typedef int arrT[10];3using arrT = int[10];45arrT& func(int i);利用尾置返回类型

1auto func(int i) -> int(&) [10]2使用decltype

1int arr[10];2decltype(arr)& func(int i);

6.4 函数重载

重载的函数在形参数量或者类型上要有所不同。p207不允许两个函数除了返回类型外其他所有的要素都相同。p207一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。p208

1//例子2void func(int* p);3void func(int* const p);底层const可以实现函数重载

1void func(int* p);2void func(const int* p);

6.4.1 重载与作用域

不要在局部作用域内声明函数。p210在内层作用域中声明的名字,将隐藏外层作用域中的同名实体,在不同作用域中无法重载函数名。p210

6.5 特殊用途语言特性

6.5.1 默认实参

一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值,否则会导致二义性。p211函数调用时按其位置解析,默认实参负责填补函数调用缺少的==尾部==实参,所以尽量让不怎么出现默认值的形参出现在前面让经常使用默认值的形参出现在后面。p212

1int func(int x = 10, int y = 20, int z = 30); //声明

1//调用2func(, , 30); //错误函数多次声明,后续的声明只能为之前没有默认值的形参增加默认实参,而且形参右侧参数必须都有默认值。p212

1int func(int x, int y, int z = 20); //第一次声明2int func(int x, int y = 30, int z); //第二次声明,此时y默认值为30,z默认值为20函数声明中有了默认值,函数定义中不能有默认值。局部变量不能作为默认实参, 此外的表达式类型只要能够转换成形参所需类型就可以作为默认实参。

6.5.2 内联函数和constexpr函数

内联函数可避免函数调用的开销,适用与优化规模较小、流程直接、频繁调用的函数。p214cosntexpr函数的返回值类型以及所有形参的类型都得是字面值类型,且有且仅有一条return语句。p214

1//只有实参是常量表达式时,返回值才是常量表达式2constexpr int func(int x)3{4    return x * 10;5}内联函数和constexpr函数的定义应该在头文件内,因为编译器要在调用点内联展开只有函数原型是不够的,同时确保所有源文件的定义完全相同。p215

6.5.3 调试帮助

assert宏定义在cassert头文件由预处理器而非编译器管理。p215

1assert(expr) //expr为假时输出信息并终止程序如果定义了NUEBUG预处理变量,则assert什么也不做。p216

6.6 函数匹配

函数匹配是从可行函数中选择与本次调用最匹配的函数,实参类型与形参类型越接近,匹配效果越好。p218

1//声明2int func(int, int);3int func(double, double);4

1//调用2func(3, 3.14) //无法判断孰优孰劣,产生二义性报错

6.7 函数指针

函数指针指向函数类型,函数类型由它的返回类型和形参类型共同决定,与函数名无关。p221

1void swap(int& a, int& b);2void (*pf)(int&, int&); //函数指针当把函数名作为一个值使用时,它会自动转换成指针。p221可以直接用指针名调用函数,无需解引用。p221

1int x = 10, y = 20;2//三种调用等价3pf(x, y);4(*pf)(x, y);5swap(x, y);不能定义函数类型的形参,但形参可以是指向函数的指针。p222

1//多次swap2void func(int n, int& x, int& y, void (*pf)(int&, int&))3void func(int n, int& x, int& y, void pf(int&, int&)) //等价定义,函数类型自动转换成函数的指针4

1func(3, x, y, swap) //调用,函数名自动转换成函数指针可用类型申明和decltype简化类型名。

1typedef decltype(swap) *swapP;和数组类似,不能返回一个函数,但可以返回指向数组的指针,和函数类型形参不同,返回类型不会自动地转换成指针。p223

标签:

X 关闭

X 关闭

热门文章