• C++ 函数


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

    一,函数基础

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

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

    函数的调用:

    int main(){
       int a = 20;
       int b = 40;
       int c = sum(a,b);//形参调用
       int d = sum(20,40);//实参数调用
       cout << "a + b = "<
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    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) <
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

    2,函数的声明

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

    
    class study
    {
    public:
    	void setAge(int age); //函数的声明
    private:
    	int age;
    };
    
    //函数的定义
    void study::setAge(int age)
    {
        this->age = age;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    二,参数传递

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

    1,传值参数

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

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

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

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    把指针作为形参进行传递,然后修改指针所指向的对象,然后 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<

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

4,const形参和实参

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

void read(const int pi) {
    pi = 20;
}
  • 1
  • 2
  • 3

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

5,数组形参

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

void print(const int*);
void print(const int []);
void print(const int [10]);
  • 1
  • 2
  • 3

注:使用数组作为形参时,也确保使用数组时不会越界。
使用数组作为形参时,需要结合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" });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

三,返回类型和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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
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
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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

const string& change() {
    string ret;
    if (!ret.empty())
    {
        return ret; //返回局部对象的引用
    }
    else {
        return "Empty";//返回了局部临时量
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

如上两种方式都是错误。

3,返回数组指针

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

int arr [ 10 ] ;// arr是一个含有10个整数的数组
int * pi[10];//pi 是一个含有10个指针的数组
int (*pr)[10] = &arr;//pr 是一个指针,指向含有10个整数的数组
  • 1
  • 2
  • 3

四,函数重载

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

void print(int a);
void print(int a,int b);
void print(int a,int b,int c); 
  • 1
  • 2
  • 3

编译器会根据传递的实参的类型和个数推断出调用的是哪个函数。
注:
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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

2,内联函数

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

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

int main() {
    print("Hello China");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

3,constexpr函数

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

constexpr int new_sz() {
    return 42;
}
constexpr int foo = new_sz();
  • 1
  • 2
  • 3
  • 4

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

六,函数指针

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

1,函数指针的基本使用

例如:

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

void (*pr)(const string&);

int main() {
    pr = print;
    pr("Hello Word");
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
3,函数指针作为参数

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

void fun1(const int& a, bool pf(const string&, const string&)) {
    
}
  • 1
  • 2
  • 3

2)显式声明

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

}
  • 1
  • 2
  • 3

在使用时,可以直接传入一个函数,和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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

  • 相关阅读:
    Docker 部署Harbor 443端口冲突
    JavaSE基础加强、数据结构
    【论文阅读】通过3D和2D网络的交叉示教实现稀疏标注的3D医学图像分割(CVPR2023)
    第二章 USB应用笔记之USB通讯基础
    跨境电商建站:选择域名需要注意什么?
    Spring IOC 01
    Windows 实时语音转文字|免费语音视频翻译转文字|语音会议记录方案
    TypeScript16:抽象类
    壹号店API接口 获取商品详情
    【Mysql高级特性】 初探 InnoDB 体系架构
  • 原文地址:https://blog.csdn.net/zdc9023/article/details/126347916