C++ primer读书笔记4(函数)

1 函数的返回值
在早期的C++中,C++ 函数可以不指明翻译值,这个时候系统会默认返回类型是int如
main()
{do something;},但是在现在的C++标准中函数没有返回类型就是错误的。


2 函数参数的传递
函数的参数的传递在C++ 中分为三种类型,分别是传值调用,传引用调用,传指针调用。
(1)传值调用
传值调用就是把实参的值传递给形参,形参此时是实参的副本,函数调用完毕就会消去形参。比较占用时间和内存,但是不会修改实参的值



需要注意的如果函数的形参是非引用的 const类型,那么编译器仍然会将函数声明为非const类型的形参,这么做的目的是为了兼容C语言。
如:
void func(const int ){};= void func(int){}
(2)传指针调用
此时同样会创建实参指针的副本,但是用指针调用,可以修改实参指针所指的对象。有时候占用空间和时间都比较少。
(3)传引用调用
此时函数的形参代表的是实参的引用,对形参做什么操作就相当于对实参做什么养的操作。传引用调用的优点就是不用复制对象,节省空间


和时间,这样起到和传指针调用相同的结果,并且不会出现像指针那样的复杂操作。
注意:const 类型的引用和非const类型引用的区别
因为前面讲过可以将const类型的变量,非const类型的变量,不同类型的变量,字面值常量赋给一个const类型的引用,但是对于非const类


型的引用,它只能引用非const类型并且数据类型完全一样的类型。要不然他就会修改传递的值。所以在参数作为引用类型,并且不希望修改


实参时候,最好把参数定义为const类型的引用,这样会增加函数的灵活性和通用性。


3 数组作为参数
数组作为函数的参数有三种方法来声明。
int array[10];
void func(int *p);
void func(int p[]);
void func(int p[10]);
或者
void func(int *);
void func(int []);
void func(int [10]);
以上定义都是等价的,形参类型都被解释int*,编译器会忽略传递进来的数组的长度,所以这里写个10也是徒劳无功,如果非要传递数组的


长度,那么就要重新加一个数组长度的参数。编译器仅仅检查传进来的实参的类型是否为指针类型,是否和数组元素相匹配。


4 通过引用传递数组
通过引用传递数组时,编译器不会将数组实参转换为指针,而是传递数组的引用本身,同时数组的大小也是作为传递的一部分,当传递的数


组长度不等于形参中引用数组的长度时就会报错。形式为:
void func(int (&)[10]);
void func(int (&p)[10]);


5 多维数组的传递
多维数组中除了第一维外,所有维的长度都是元素类型的一部分,所以必须明确的声明。例如:
void func(int (*p)[2]);
void func(int p[][2]);
这两者都是等价的,形参表示一个指针,指向包含2个元素的数组。同理也可以使用引用
void func(int (&p)[2][2]);
void func(int (&)[2][2]);这个时候编译器会检查数组的第一维的长度,如果不符合就会报错。


6 可变参数的函数
可变参数的函数有两种,一种是有一定数目实参的,后面是可变形参,另外一种是全部是可变形参的,对应的形式为:
void func(int ,...);// 最后的逗号‘,’是可以不要 的
void func(...);
这两种函数的读写是需要对函数的栈结构有一定的了解,简单的话可以直接调用标准库函数,头文件是stdarg。


7 return
不带返回值的return语句只能用于返回类型为void的函数,在返回类型为void的函数中,return返回语句不是必须的,隐式的return语句将


在最后一个语句完成时。


返回类型为void的函数可以返回一个返回类型是void的函数的调用结果


关于main函数的返回值,main函数没有返回值就可以结束。如果程序运行到最后一个语句还没有返回,那么编译器会隐式的插入返回0的语句





8 返回引用
返回引用增加了函数的灵活性,但是对函数的返回值有一定的要求,函数的返回值必须是在函数调用结束后不被释放内存的对象,所以要么


传进的参数是引用,然后返回引用,或者引用的一部分,要么传进的参数是指针,返回整个指针所指的内容,或者指针所指的一部分,例如



int &func(int &x,int &y)
{
return x>y?x:y;
}


char &get_val(string &str,string::size_type ix)
{
return str[ix];
}


int &func(int *p)
{
return *p;
}


int &get_val(int *array,int index)
{
return array[index];
}


9 函数的声明
一个函数可以声明多次,但是只能定义一次,所谓的函数原型就是函数的返回值,函数的名字,函数的参数表


10 默认实参


函数的形参从左到有,如果其中一个参数为默认参数的话,那么往右边(后边的)参数也必须都是默认参数


一个编译编译单元中函数的默认实参只能指定一次,如果一个编译单元中在函数声明中为函数指定默认实参,又在函数定义中为函数指定默


认实参,那么就会就会造成默认实参的重定义,因此一般都在头文件中指定函数的默认实参。


11 局部对象
在函数体中定义的对象叫做局部对象,局部对象又分为自动对象和静态局部对象,只有当定义对象的函数被调用时才存在的对象被称为自动


对象(auto object)。
如果一个生命期跨越了这个函数的多次调用,这种变量应该被声明为static。static这种变量比较奇特,因此如果在函数内部初始化它的话


,静态变量初始化发生在编译阶段,而不是第一次调用,通过调试就可以发现。


12
操作系统中内存的分段:
操作系统中的进程的内存分段:
1)代码段:代码段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存中的映像。代码段需要防止在运行时被非法修改,


所以只准许读取操作,而不允许写入(修改)操作。


2)数据段:数据段用来存放程序静态分配的变量和全局变量,数据段分为一般数据段和BSS段,一般数据段即上面的.data段,用来存放已初


始化全局变量。


3)BSS段:BSS段包含了程序中未初始化的全局变量,在内存中 bss段全部置零。


4)堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用C语言的malloc或C++的


new等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用C语言的free或C++的delete等函数释放内存时,被释放的内


存从堆中被剔除(堆被缩减)。


5)栈:栈是用户存放程序临时创建的局部变量,也就是说我们的花括号"{}"中定义的变量(但不包括static声明的变量,static意味着在数


据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放


回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据


的内存区。


13 inline 函数
关于inline函数,已经写了一篇inline函数的笔记,这里就不多说,反正谨记inline不可滥用,inline函数要放在非编译单元,inline只对


函数体有用。


14 关于类的成员函数
(1)this 指针:每个类的成员函数(不包含static)都有一个隐含的形参,在调用成员函数时,形参this都被初始化为调用对象的地址。
(2)const成员函数:
void classA::func()const
{...}
//用const修饰函数,那么const改变了this形参的类型,this变成指向const对象的指针,所以对象中的成员将不会允许被改变。
(3)默认构造函数
没有参数或者参数全部为默认构造函数的构造函数时默认构造函数,由编译器创建的默认构造函数被称为合成的默认构造函数。
对于合成的默认构造函数其中对象的初始化要看类对象作用域以及类成员变量的类型。首先对于成员类型是类型的话,就会调用该类的默认


构造函数;对于内置类型的成员变量,依赖于类对象的定义,如果该类是在全局定义或者静态定义的,那么内置类型的成员变量就被初始化


为0;如果类对象时局部定义的,那么内置类型的成员变量就不被初始化,或者依赖于编译器。


15 关于重载函数
(1)定义:函数名字相同,但是参数类型或者参数数目不相同的函数被定义为重载函数。函数的返回类型以及形参名称不在检查的范围,所


以不能通过这些东西来定义重载函数。
(2)重载函数作用域:当调用函数时,编译器会在调用的作用域查找是否有调用函数的名字,如果找到的话,就不会跳出该作用域到其外围


去寻找。如:
void print(int);
void func()
{
void print(char);
print(3)//error
print('a');//ok
}
这个例子中func函数中的void print(char)会屏蔽其作用域外面的void print(int);
(3)函数的匹配 步骤:
找出函数名字相同的;
找出可行函数,就是一一匹配实参和形参,其中实参能够通过类型提升或者隐式转换最终转变成形参的类型
选出最佳匹配。
(4)匹配等级:编译器又会将转换等级分成以下四种等级(从高到低)
精确匹配
通过类型提升来匹配
通过标准转换来匹配
通过类类型转换来实现匹配
对于任意类型的实参值,int类型总是比short类型的匹配,如:
void ff(int);
void ff(short);
ff('a');// 此时会选择void func(int),而不是void ff(short);
(5)含有参数枚举类型的
对于枚举类型的对象,其初始化只能采用两种方式,一种是该枚举类型的枚举成员,另一种是该枚举类型的另一个对象。如:
enum TT{A,B,C};
void  ff(TT);
void  ff(int);
int main()
{
TT cur = A;
ff(0); //调用ff(int) 
ff(A); //调用ff(TT)
ff(cur); //调用ff(TT)
return 0;
}
因此无法将int类型的转化为枚举类型(编译时会提示:不能讲参数1从“int”转换成“TT”),但是可以把枚举类型的转化为形参,枚举类


型被提升为int类型或者更大类型的。


16 形参中的const 
(1)如果形参是非引用类型,那么const修饰对参数类型无贡献,例如:
void ff(int xx);
void ff(const int xx);
void main()
{
ff(3);
}


void ff(int xx)
{
std::cout<<"int parament used"<<endl;
}
void ff(const int xx)
{
std::cout<<"const int parament used"<<endl;
}
在编译的时候会提示错误: error C2084: 函数“void ff(int)”已有主体
编译器这么做的原因是为了兼容C语言,这样凡是用到const修饰的变量,都可以让C语言类型的参数来调用。但是这并不说明这两种函数就完


全一样了,这样只能说明不能通过添加const来实现重载,但是要是用到了const形参的话,那么传递的实参值是不能修改的。


(2)仅仅有引用或者指针的地方才能用
void ff(int &xx);
void ff(const int &xx);
这两个函数时不同类型的函数,我们可以把非const类型的变量传递给const类型的引用,但是不能const类型的变量传递给非const类型的引


用,因此如果不想改变变量的值话,那么最好将变量定义为const类型的。
同理:
int calc(char*);
int calc(const char*);
也是两种不同类型的函数。


17 指向函数的指针
(1)bool (*func)(int,int);
这个语句将func 声明为一个指向函数的指针,函数的返回值是bool,参数是两个int。
(2)指针的初始化
typedef bool (*func )(int,int);
这是将 func声明为指向函数的指针。例如:
bool length(int,int);
func ff = length;
或者:
func ff = &length;
通过函数名或者函数名取地址操作可以获取该函数的地址。
(3)通过指针调用函数
func ff = length;
ff(1,3);
(*ff)(1,3);
这两种调用方式完全等价。
(4)函数指针作为形参
typedef bool (*func)(int,int);
bool test(int,int);
void funct(int,int func);
void funct(int,int,bool(int,int));
void funct(int,int,bool (*)(int,int));




void main();
void funct(int,int,func)
{}
void funct(int,int,bool(int,int))
{}
void funct(int,int,bool (*)(int,int))
{}
最后两个函数体都会产生这样的错误: error C2084: 函数“void funct(int,int,bool (__cdecl *)(int,int))”已有主体
其中参数中的 函数返回类型bool 和 后面的形参列表的空格可以有可以无。


18 返回函数指针的函数
这又是一种看起来比较奇怪的格式:
bool (*ff(int))(int,int);
解析时从内向外看,函数名称ff ,参数类型int,即:ff(int),返回类型是一个指向函数的指针(bool (int,int))。
如果想简单的点,可以这样搞:
typedef (*func)(int,int);
func ff(int);


19 指向重载函数的指针
指针的类型必须跟重载函数的版本精确匹配,要么就会出错。
void func(unsigned int );


void main()
{
void (*ff)(unsigned int) = &func; //OK
void (*ff)(int) = &func;//error
}


20 关于颠倒名字的叫法
数组指针,指针数组
函数指针,指针函数
这些省略的简化叫法真的很讨厌人,让人经常搞混,但是现在可以这么想,只看其本质,数组指针,修饰词是数组,本质是指针,那么它的


意思就是只想数组的指针;同理指针数组表示的是一个数组,它里面的元素师指针。
函数指针 表示的是一个指针,指向的内容是一个函数;指针函数就代表的是一个函数,其返回值是一个指针。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值