6 函数
6.2 函数的定义
方法就是接口
形参是角色,实参是演员。
返回值不能是数组,但可以是其他任何类型,如指针、结构体和共用体。
6.3 函数声明
函数声明描述了函数和编译器间的接口,想要调用一个函数,必须在调用函数中对被调用函数进行说明
在调用函数中对被调用函数进行声明,使其可用。
6.3.2 声明一个函数
C++的函数声明只写变量类型即可。
作用:
- 使编译器正确处理返回值
- 使编译器检查输入的数目
- 使编译器检查输入的类型,并对相关类型进行类型隐式转换
6.3.3 分割程序文件
将函数定义在cpp文件中,将函数声明放在同名(可以不同名但为了标识设为为同名)的h文件中,这样可以通过编译器进行编译预处理h文件实现函数声明。
头文件声明的函数中的头文件对本文件无效。
6.4 函数调用
- 实参向形参传递数据
- 为获得数据的参数和函数中声明的变量分配临时存储空间
- 中断当前函数,将函数流程转到被调用函数的入口
对于值传递和指针传递来说,被调用函数将返回的值或者指针存储在CPU寄存器或某块内存区域,然后调用函数访问这块内存区域。当被调函数执行完,若返回指针指向这块内存地址已经被释放掉,访问已经释放掉的内存会给程序带来致命的问题。
6.4.6 函数返回值
#include<iostream>
using namespace std;
int main()
{
int *minus(int ,int);/*传指针返回*/
long &multiply(int,int );/*传引用返回*/
int x=2,y=3;
int *minusans=minus(x,y);
long *mul=&multiply(x,y);/**mul=&(*z)*/
cout<<"2-3="<<*minusans<<endl;
cout<<"2*3="<<*mul<<endl;
delete minusans;
delete mul;
return 0;
}
int *minus(int m,int n)
{
int *z=new int;/*z自动消亡,但p指向的int型内存空间不会释放掉,代码退出后,由主程序释放这块动态申请的内存*/
*z=m-n;
return z;
}
long &multiply(int m,int n )
{
long *z=new long;
*z=m*n;
return *z;
}
6.4.7 默认参数调用
- 参数默认必须按照从后往前的顺序
- 和函数声明一样,在函数调用时省略某个参数,必须省略其后所有后续参数
6.4.8 inline函数
在调用函数前,应保护被调函数现场,记录程序当前位置以方便从被调函数中返回,恢复现场,并从原来的位置处继续执行。
对一些很短、频繁调用的函数体,系统开销无法忽视。
使用函数可以提高程序的可读性,方便阅读和修改(函数封装起来,提供接口,实现可变),用于共享。
定义inline 函数的方法,在普通函数的定义前加修饰符inline即可。
注意:
- 在一个文件中定义的inline 函数不能在另一个文件中使用。
- inline函数体中,不能有循环语句、if语句或switch语句,否则,即使函数定义时使用了inline 关键字,编译器也会将其当成普通函数来处理
- inline 函数应在调用和声前进行定义
#include<cmath>
#include<iostream>
using namespace std;
inline double * Func(int a)
{
double *z=new double(0);
*z=cos(a);
return z;
}
int main()
{
double * Func(int);
double *a=Func(1);
cout<<*a<<endl;
return 0;
}
6.5 递归
任何用递归编写的函数都可用循环代替,以提高效率。但是。递归带来的好处也是显而易见。
- 程序的可读性比较好,易于修改和维护
- 在函数不断调用中,函数的规模在减小
6.6 函数的重载
6.6.1 何时使用函数重载
形参的类型应不同,对于形参类型相同,只有形参个数不同的场合,就无需定义两个函数,采用默认参数调用机制,只要定义一个函数即可。(对于无法满足默认函数调用机制要求的可在函数体第一行赋值)
6.7 C++如何使用内存
c++有3种管理数据内存的方式即自动存储(栈)、静态存储和动态存储
栈 | 堆 |
---|---|
系统管理 | 用户管理 |
系统决定生存期 | 用户决定生存期 |
无泄漏问题 | 内存泄漏问题 |
6.7.1 自动存储
- 栈
- auto 关键字 (默认)
- register 关键字 通知编译器,用户希望通过CPU寄存器处理变量,加快变量的访问速度。
缺点:寄存器是没有内存地址的,不能对 register自动变量进行取地址操作
6.7.2 静态存储(编译器预分配)
静态存储的变量可分为全局变量和静态变量、
- extern关键字 全局变量
extern 声明的变量称为全局变量,意味着可以在程序种任何位置使用,全局变量是在函数和类外定义的。
全局变量存在,定义性声明和引用性声明,定义性声明用于创建变量(初始化表达式不可省略)
只要在外部定义的变量,编译器就将其当作全局变量。extern 可省略,此时初始化表达式可省略。
只要在使用全局变量前,需要对其进行引用性声明,这和函数类似,引用性声明只有一种格式。
定义在前,使用在后,定义和声明用在同一个文件中可省略引用
区别:
- 定义性声明只有一次,引用性声明可以有多次
- 定义性声明在外部,引用性声明没有限制
- 定义性声明有两种格式:带extern(不可缺少初始化语句)和省略extern的形式(可省略初始化语句,编译器默认初始化),而引用性声明只有带extern 的一种。
2. **static关键字 ** 静态变量
-
没有定义性声明和引用性声明之分,只有声明语句
即可外部声明又可内部声明。外部声明时,只能有一个文件中使用,内部声明时所在代码块内使用。内部静态变量与自动变量唯一区别就是内部静态变量具有永久生存期。
- 全局变量和静态变量的初始化
只能使用常量表达式来初始化全局变量和静态变量。常量表达式包括直接常量、const 常量、枚举常量和sizeof()运算符。不能用变量初始化全局变量和静态变量。全局变量和静态变量可以相互赋值,前提时变量可见。
6.8 作用域与可见域
存在、有效、使用对应变量的生存期、作用域和可见域。
使用**作用域::可使被屏蔽的全局变量在局部可见**
6.8.3 函数的作用域和可见域
使用关键字static将函数声明为内部的,只能在本文件种使用该函数,在函数定义和函数声明中都要使用static关键字。static将屏蔽其他文件种外部定义的同名函数
7.1.2 野指针
指针被free或delete一定要置为null,指针指向的内存已被赋予新的意义。
也可称为野指针的情况
- 未初始化指针
- 指向临时变量的指针[改为动态内存申请即可]
7.1.4 用错sizeof
当数组作为函数参数进行传递时,数组退化为同类型的指针,用sizeof无法取得数组的大小。
7.1.5 内存越界访问
写越界,即往不该写的内存地址空间中写了东西,会带来匪夷所思的错误和BUG,有些症状是随机的。
7.1.6 养成变量初始化的习惯
7.2.1 参数传递时的“副本”
值传递和指针传递都有“副本”。
指针传递,将指针的值复制(如初始值NULL),副本和外部指针同时指向相同的地址,若在函数中对指针值(NULL)修改,则无法对外部指针产生任何影响(依旧是NULL)。可以通过间接引用对指针指向的内存进行修改。
因此可以使用PointerToPointer用二级指针对指针的值进行修改。
#include<iostream>
using namespace std;
void GetMem(char **p,int num)
{
*p=new char[num];/*对传入二级指针所指的指针的内存修改*/
cout<<"p在内存中的地址:"<<p<<endl;
}
int main()
{
char *pChar=NULL;
cout<<"pChar在内存中的地址:"<<&pChar<<endl;
GetMem(&pChar,10);/*pChar指针的地址*/
if(pChar!=NULL)
{
cout<<"内存申请成功"<<endl;
delete [] pChar;
}
else
{
cout<<"内存申请失败"<<endl;
}
return 0;
}
7.3 函数与指针
7.3.1指向函数的指针
- 函数指针的声明与初始化
返回类型 (*指针名) (参数列表)
函数指针参数不存在类型转换
void 指针的特点
- void指针没有类型。
- 任何类型的指针都可赋给void指针,且无需任何类型转换,void指针也只获得该类型变量的地址而不获得大小。
- void指针转换为其他任何类型的指针都需要类型转换。
- void指针在转化u类型前不能解引用和进行指针运算。
#include<iostream>
#include<cmath>
using namespace std;
void Func(int a)
{
cout<<sqrt(a)<<endl;
}
void Funa(int b)
{
cout<<sqrt(b*b*b)<<endl;
}
int main()
{
void (*a)(int);/*函数指针声明*/
a=Func;/*函数指针赋值*/
a(10);/*指针形式调用*/
a=Funa;
a(10);
return 0;
}
-7.3.2 typedef
给一个已经存在的类型声明一个别名
typedef int* int_ptypedef;
#define int_pdefine int *
const int_ptypedef p1;/*声明const 指针p1,其指向可改*/
const int_pdefine p2;/*常量指针,指针可改*/
typedef void (*a)(char **,int ) ;
a h;
h=GetMem;
h(&pChar,10);/*pChar指针的地址*/
7.3.3 通过函数指针将函数作为另一个函数的参数
7.3.4 函数指针数组
增强条理和可读性,不同类型的函数指针可用链表。
#include<iostream>
#include<cmath>
using namespace std;
typedef void (*hs)(int);
void Just_It(int a)
{
cout<<a<<" ";
}
void Add_lt(int a)
{
cout<<a+1<<" ";
}
void Sub_It(int a)
{
cout<<a-1<<" ";
}
/*通过函数指针将函数作为另一个函数的参数*/
void Call(char *name,hs func)
{
int arr[]={1,2,3,4,5,6,7,8,9};
cout<< name<<endl;
for(int i=0;i<9;i++)
{
func(arr[i]);
}
cout<<endl;
}
int main()
{
char *name[]={"本身","+1","-1"};
hs func[]={Just_It,Add_lt,Sub_It};
for(int i=0;i<3;i++)
{
Call(name[i],func[i]);
}
return 0;
}
7.3.5 返回函数指针的函数
选择执行函数
#include<iostream>
#include<cmath>
using namespace std;
typedef void (*hs)(int);
void Just_It(int a)
{
cout<<"对"<<a<<"输出本身";
cout<<a<<endl;
}
void Add_lt(int a)
{
cout<<"对"<<a<<"+1输出本身";
cout<<a+1<<endl;
}
void Sub_It(int a)
{
cout<<"对"<<a<<"-1输出本身";
cout<<a-1<<endl;
}
hs lookup(int a)
{
switch (a)
{
case 0:
return Just_It;
case 1:
return Add_lt;
case 2:
return Sub_It;
}
}
int main()
{
for(int i=0;i<3;i++)
{
hs func=lookup(i);
func(3);
}
return 0;
}
-7.6 函数编写建议
-7.6.1 合理使用const
在指针传递或者引用传递时,如果参数仅仅是输入用,则应该在类型前加const,防止指针在函数体内被意外修改。
对于非内部数据类型的输入参数,应将值传递改为const 引用传递,以提高效率