C++ 函数

在C++中函数也支持重载(函数名相同,但参数不同)和重写(继承时重写父类的函数)

一,函数基础

一个典型的函数的定义包括以下部分:返回类型,函数名,形参(0个或多个组成的列表),函数体。
形参以逗号隔开,位于一对圆括号之内。函数执行的操作在函数体内。
例如:求两数之和

int sum(int a, int b) {
    int result = a + b;//局部变量,用于保存计算的结果
    return result ;//返回结果
}

函数的调用:

int main(){
   int a = 20;
   int b = 40;
   int c = sum(a,b);//形参调用
   int d = sum(20,40);//实参数调用
   cout << "a + b = "<<c<<endl;
   return 0;
}

函数在调用时也可分为:形参调用和实参调用;在函数调用时要根据函数定义的参数类型,传入对应的类型参数才能正常的调用。

1,局部对象

在C++中不同作用域的对象都有对应的生命周期,名字的作用域是程序文件的一部分,名字在其中可见,对象的生命周期是程序执行过程中该对象存在的一段时间。
形参和函数体内部定义的变量统称为局部变量,他们对函数而言是局部的,仅在函数的作用域内可见,同时局部变量还会屏蔽外层同名的变量。
函数体之外的对象存在于程序的整个执行过程,在程序启动时创建,终止时销毁,局部变量的生存则依赖于定义的方式。
1)自动对象
函数体内部定义,调用函数时执行对象创建时,创建该对象,当函数执行到末尾时,销毁它,只存在于函数执行期间的对象。
例如形参就是一种自动对象,在函数开始时为形参申请存储空间,函数终止时就被销毁。
2)局部静态对象
在函数调用时创建,函数执行完成后依然存在的对象,使用static进行修饰,他的生命周期只到程序终止才被销毁。
例如:

int count(int num) {
    static int res = 0 ;
    return res + num;
}

int main() {
    int a;
    cin >> a;
    cout << count(a) << count(a) <<endl;
    return 0;
}

如上函数,多次调用count(a),其结果会基于上一次的结果进行累加,因为res 是static类型的,程序运行期间会一直在内存中。

2,函数的声明

函数只能被定义一次,但可以声明多次,函数的声明无需函数体,使用分号代替。函数的定义则是对函数的具体实现。
函数一般是在头文件中声明,在源文件中定义。
例如:


class study
{
public:
	void setAge(int age); //函数的声明
private:
	int age;
};

//函数的定义
void study::setAge(int age)
{
    this->age = age;
}

二,参数传递

每次调用函数时,都会重新创建他的形参,并传入实参对形参进行初始化。
当形参数是引用传递或传引用调用时,引用形参是它对应的实参的别名。

1,传值参数

当实参的值拷贝给形参时,形参和实参是两个独立的对象。
当初始化化一个非引用类型的变量是,初始值被拷贝给变量,对变量的改动不会影响初始值。
例如:

int a = 10;//int 类型的初始变量
int b = a;//b 是a 的值的副本
b = 20;// b的值改变,不会影响a

函数对形参做的所有操作都不会影响实参。

2,指针形参

指针的行为和引用类型一样,当对指针执行拷贝是,拷贝是是指针的值,拷贝之后两个指针是不同的指针。
例:

void enlarge(int* pr) {
    *pr = *pr * 10;
}

int main() {
    int ab = 10;
    int* pb = &ab;
    enlarge(pb);
    cout << "enlarge ab:" << ab << endl;
    return 0;
}

把指针作为形参进行传递,然后修改指针所指向的对象,然后 ab的值也发生了变化,结果输出100。

3,传引用参数

对引用的操作实际作用于所引用的对象本身。
例如:

void reduce(int& pr) {
    pr = 20;
}
int main() {
    int ab = 10;
    int ac = 20;
    int &pr = ab;
    reduce(pr);
    cout << "enlarge ab:" << ab << ac<<endl;
    return 0;
}

通过引用pr修改ab的值,最后打印结果ab的值也变成了20。
当操作毕竟大的对象或容器时,使用拷贝的方式效率低下或不支持,因此使用引用传递可以避免拷贝提升效率
注:函数无需改变引用形参的值时,使用常量引用。

4,const形参和实参

当const为顶层时,只能访问和拷贝,不能进行修改。
例如:

void read(const int pi) {
    pi = 20;
}

当形参被const修饰后,就变成只能读取,不能修改,如上代码编译器会直接报错。
注:尽量使用常量引用,这样可以防止对象被修改。

5,数组形参

使用数组作为形参时,不允许拷贝数组,使用时将其转换为指针。
使用数组作为形参时有如下表现形式

void print(const int*);
void print(const int []);
void print(const int [10]);

注:使用数组作为形参时,也确保使用数组时不会越界。
使用数组作为形参时,需要结合begin和end来进行使用,这样可以有效的防止下标越界。

6,可变形参initializer_list

如果函数的实参数量位置但全部实参的类型相同,可以使用initializer_list

函数描述
initializer_list lst;默认初始化:T类型元素的空列表
initializer_list lst{a,b,c};lst 的元素数量和初始值一样多,lst的元素是对应初始值的副本,列表中元素是const
lst2(lst);拷贝或赋值一个initializer_list对象不会拷贝列表中的元素;拷贝后原始列表和副本共享元素
lst.size();列表中的元素数量
lst.begin();返回指向lst中首元素的指针
lst.end();返回指向lst中尾元素下一位置的指针

和vector不同的是,initializer_list对象中的元素永远是常量值,无法改变initializer_list对象中元素的值。
例如:

void request(initializer_list<string> lst) {
    for (auto beg = lst.begin();beg != lst.end();++beg)
    {
        cout << *beg << endl;
    }
}
//使用时:
request({ "one","two","three" });
request({ "one","two","three","four" });
三,返回类型和return语句

return语句的作用:终止当前正在指向的函数并将控制权返回到调用该函数的地方
常见的表现形式有两种:
return;
return expression;

1,无返回值函数

无返回值的函数只能用在返回类型是void的函数中,在void的函数中不要求一定要有return语句,因为在代码执行到最后会隐式地执行return
如果无返回值函数,想要在代码中提前退出,则可以调用return回到函数调用的地方。
例如:

void change(int a,int b){
    if( a > b)return;
    int temp = a;
    a = b;
    b = temp;
}
2,有返回值函数

return语句可以返回函数的结果,只要函数的返回类型不是void类型。
return语句返回值的类型必须于函数的返回类型相同,或能隐式地转换成函数的返回类型。

int getMax(int a, int b) {
    if (a == 0 || b == 0) return 0;
    if (a > b)
    {
        return a;
    }
    else {
        return b;
    }
}

1)返回值的实现过程
返回一个值的方式和初始化要给变量或形参一样,返回的值用于初始化调用点的一个临时量,该临时量就是函数的调用结果。
2)不要返回局部对象的指针或引用
函数调用完成后,它所占用的存储空间也会随之被释放掉,局部指针或引用将会失效。
例如:

const string& change() {
    string ret;
    if (!ret.empty())
    {
        return ret; //返回局部对象的引用
    }
    else {
        return "Empty";//返回了局部临时量
    }
}

如上两种方式都是错误。

3,返回数组指针

因为数组不能被拷贝,所以函数不能返回数组,而是返回一个数组的指针或引用。

int arr [ 10 ] ;// arr是一个含有10个整数的数组
int * pi[10];//pi 是一个含有10个指针的数组
int (*pr)[10] = &arr;//pr 是一个指针,指向含有10个整数的数组
四,函数重载

同一作用域内的几个函数名字相同但形参列表不同。
例如:

void print(int a);
void print(int a,int b);
void print(int a,int b,int c); 

编译器会根据传递的实参的类型和个数推断出调用的是哪个函数。
注:
1)重载和const修饰的形参是无法区分的,const只是一个修饰符。
2)当操作执行同一个操作时,所需参数不同时使用重载。
编译器对重载的处理
1)找到一个与实参最佳匹配的函数,并生成调用该函数的代码
2)找不到任何一个函数与调用的实参匹配,此时编译器就会报错
3)如果出现多个匹配的函数,就会出现二义性的错误

五,特殊用途语言特性

默认实参,内联函数,constexpr函数等

1,默认实参

函数在多次调用时使用了同样的形参,这是可以使用默认实参,这样调用时就可以省略掉
例如:

void print(string str = "Hello ,Word!") {
    cout << str << endl;
}

int main() {
    print();
    print("Hello China");
    return 0;
}

默认实参一般出现在头文件中,如果正常的形参和默认实参同时存在时,默认实参防止函数的最后面。

2,内联函数

在编译时,会被拷贝到调用的地方,减少函数调用的开销,使用时需要使用inline进行修饰
例如:

inline void print(string str = "Hello ,Word!") {
    cout << str << endl;
}

int main() {
    print("Hello China");
    return 0;
}

注:内联机制适合函数规模较小,流程直接,频繁调用的函数,在内联函数中不要使用递归,因为有些编辑器不支持。

3,constexpr函数

修饰用于常量表达式的函数。
定义时,函数的返回类型及所有形参的类型都得是字面值类型,且函数体中必须有且只有一条return语句。

constexpr int new_sz() {
    return 42;
}
constexpr int foo = new_sz();

执行该初始化任务时,编译器会把constexpr函数的调用替换成其结果值。

六,函数指针

函数指针指向的时函数而非对象。

1,函数指针的基本使用

例如:

void print(const string &str) {
    cout << str << endl;
}

void (*pr)(const string&);

int main() {
    pr = print;
    pr("Hello Word");
    return 0;
}
2,函数指针的赋值

函数指针的赋值也分为两种,一种直接赋值,一种使用解地址符的方式进行赋值
例如:

//定义一个函数,接收两个引用类型的参数
void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

//定义一个函数指针
void (*pr)(int& a, int& b);

int main() {
    int a = 10, b = 20;
    pr = swap;//直接给函数指针赋值
    pr(a, b);
    cout << "直接赋值==。a:" << a << "--b:" << b << endl;
    pr = &swap;//解地址符的方式赋值
    pr(a, b);
    cout << "去地字符赋值==。a:" << a << "--b:" << b << endl;
    return 0;
}
3,函数指针作为参数

使用函数指针,可以把函数作为参数进行传递。
作为参数进行传递时,函数指针的声明也分为两种
1)隐式声明

void fun1(const int& a, bool pf(const string&, const string&)) {
    
}

2)显式声明

void fun2(const int& a, bool (*pf)(const string&, const string&)) {

}

在使用时,可以直接传入一个函数,和Lambda表达式类似。
3)简化函数指针的声明
函数指针的简化声明

void swaps(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}
 
typedef bool(*FuncP)(const int&, const int&);
typedef decltype(swaps)* FunP2;

如上两个都是函数指针,他们是等价的类型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值