第六章 函数
6.1.1 局部对象
形参和函数体内部定义的变量统称为局部变量。在所有函数体之外定义的对象存在于程序的整个执行过程中。
自动对象:我们把只存在于块执行期间的对象称为自动对象。注意在函数体内部的内置类型变量将不被初始化。
局部静态对象:局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁。
我们建议变量在头文件中声明,在源文件中定义。
在头文件中进行函数声明。
函数返回类型:函数的返回类型不能是数组类型或函数类型,但可以是指向数组或函数的指针。
6.1.3 分离式编译
练习6.9
选项-o用于指定要生成的结果文件,后面跟的就是结果文件名字。已否决?
o是output的意思,不是目标的意思。
结果文件可能是预处理文件、汇编文件、目标文件或者最终可执行文件。
6.2 参数传递
当形参是引用类型时,我们说它对应的实参被引用传递。当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。
6.2.1 传值参数
6.2.2 传引用参数
回顾:通过将声明符写成&d的形式来定义引用类型。如果函数无须改变引用形参的值,最好将其声明为常量引用。
练习6.15: s是普通的引用也可以,但occurs不能是常量引用,因为程序中会修改occurs的值。假如我们把find_char函数中的第一个形参定义为普通的sting&:则只能将find_char函数作用于string对象。类似于这样的调用find_char("apple",'a',ctr)将引发下面的错误
6.2.3 const形参和实参
int * const p=&i; //const是顶层的,不能给p赋值。 回顾:指针本身是一个对象,它又可以指向另外一个对象。用名词顶层const表示指针本身是一个常量。
*p=0;//正确:通过p改变对象的内容是允许的
注意:尽量使用常量引用。使用引用类型而非常量引用会限制函数所能接收的实参类型。我们不能把const对象,字面值或者需要类型转换的对象传递给普通的引用形参。
6.2.4 数组形参
指向常量的指针不能用于改变其所指对象的值。 当函数不需要对数组元素执行写操作的适合,数组形参应该是指向const的指针。
回顾: int (*Parray) [10]=&arr; //括号是不能省略的, Parray指向一个含有10个整数的数组
int (&arrRef) [10]=arr;
尽管不能以值传递的方式传递数组,但是我们可以把形参写成类似数组的形式。(实际上传递的是指向数组首元素的指针)
void print(const int*);
void print(const int[]);
void print(const int[10]);
做练习的时候遇到一个问题,在使用argv中的实参时,一定要记得可选的实参从argv[1]开始。
6.2.3 含有可变形参的函数
如果函数的实参数量未知但是全部实参的类型都相同,我们可以使用initializer_list类型的形参。形参和函数体内部定义的变量统称为局部变量。函数完成后,它所占用的存储空间也将随之被释放掉,因此函数终止意味着局部变量的引用将指向不再有效的内存区域。
如果函数返回指针,引用或类的对象,我们就能使用函数调用的结果访问结果对象的成员。
后置版本得到递增(减)之前的值。
不要返回局部对象的引用或指针
函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值,其他返回类型得到右值。
6.3.2 有返回值函数
值是如何被返回的
返回一个值的方式和初始化一个变量或形参的方式完全一样:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。
使用引用避免拷贝:拷贝大的类类型对象或者容器对象比较抵消,甚至有的类类型根本不支持拷贝操作。当某种类型不支持拷贝操作时,函数只能通过引用形参访问该类型的对象。
练习6.34 后置版本得到递减之前的值,
6.3.3 返回数组指针
不能讲数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值。
返回数组指针的函数形式如下所示:
6.4 函数重载
如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数。
2021.1.19 大概有20多天都没有继续看c++了,从月初开始看那个程序之后就没再看C++primer了,这样不行呀。从双十一买书之后开始学的,当时学习热情还比较高。今晚打算11点多结束学习。
练习6.39
(a)肯定是非法的声明,第二行重复声明了函数calc (b)感觉会出现二义性调用 (c)合法的
6.5特殊用途预言特性
6.5.1 默认实参
一次函数调用实际包含着一系列的工作:调用前要先保存寄存器,并在返回时恢复;可能需要拷贝实参;程序转向一个新的位置继续执行。
二义性调用:是一种编译时发生的错误,造成二义性调用的原因是在函数匹配时两个或多个函数提供的匹配一样好,编译器找不到唯一的最佳匹配。
练习6.40
(b)错误,一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值。
之前写的程序:编写一个递归函数,输出vector对象的内容
void helper(vector<int>& nums, vector<int>::iterator it) {
auto e = nums.end();
if (it != e) {
cout << *it++ << endl;
helper(nums, it);
}
}
void print(vector<int>& nums) {
auto b = nums.begin();
helper(nums, b);
}
练习6.50:
(a) f(2.56,42)
6.7 函数指针
函数指针指向的是函数而非对象。函数的类型由它的返回类型和形参类型共同决定,与函数名无关。
函数指针的定义:可以将函数体的入口地址赋给一个指针变量,使该指针变量指向该函数,然后通过指针变量就可以调用这个函数。这种指向函数的指针变量称为函数指针。
从一组重载函数中选取最佳函数的过程称为函数匹配。
返回指向函数的指针:和数组类似,虽然不能返回一个函数,但是能返回指向函数类型的指针。
注意:所有指针变量,无论T表示什么类型,sizeof(T*)的值都是4,其占用的空间都是4个字节。
函数指针的应用举例:
#include <iostream>
#include <cstdlib>
using namespace std;//常量指针,不能通过常量指针去修改它指向的内容
int MyCompare(const void *elem1, const void *elem2){
unsigned int *p1, *p2;
p1=(unsigned int *) elem1;
p2=(unsigned int *) elem2;
return (*p1%10)-(%p2%10);
}
const int NUM=5;
int main(){
unsigned int a[NUM]={8,123,11,10,4};
qsort(a,NUM,sizeof(unsigned int),Mycmpare);
for(int i=0;i<NUM;++i)
cout<<a[i]<<" ";
return 0;
}
动态分配内存,此种内存分配是在程序运行中进行的,而不是在编译时就确定的,因此称为动态内存分配,用new运算符实现动态内存分配。一定要用delete回收。
刚才写的一些东西又丢失了。。。
- 静态局部变量和自动局部变量(没有加static关键字的局部变量)的区别在于,前者存放于全局数据区,地址固定,它的存储空间不会被别的变量占用,因此其值在定义它的函数执行完还能被保持。 函数内部的变量和函数的形参都是局部变量。
自定义数据类型
结构体中的字节对齐概念:字节对齐的作用不仅是便于cpu快速访问,合理利用字节对齐还可以有效的节省空间。
gcc也是默认4字节对齐。
总的来说字节对齐有一下准则:
- 结构体变量的首地址能够被其对齐字节数大小所整除。
- 结构体每个成员相对结构体首地址的偏移都是成员大小的整数倍??
- 结构体的总大小为结构体最大成员大小的整数倍
struct test
{
char a;
char b;
};//这个结果是2
char型数据自身对齐值为1字节,short型2字节,int/float型为4字节,double型为8字节。
结构变量的每个成员变量占据不同的存储空间,整个结构变量的体积是所有成员变量的体积之和。而联合和结构的区别在于,所有的成员变量都是从相同的地址开始存放的。
枚举类型的变量或常量可以自动转换为整型。