C++中的函数

1.函数重载

1.1 概念

我们在封装功能相同、用法相同的函数的时候,只是因为参数的不同,就需要定义多个版本的函数,且函数名不能重名,使用起来很不方便,并且函数重载允许定义重名的函数。

1.2 函数重载的要求

函数名相同,形参列表必须不同:个数或者类型不同都可以,函数重载对返回值没有要求。

1.3 重载函数的调用

调用的时候会根据,实参的不同,自动选择调用哪个函数。编译器在编译的过程中,其实已经根据类型的不同,生成了不同名字的函数。

1.4 函数的默认参数

1.C语言中函数的形参的值必须由实参传递过来;而C++中,允许给函数的形参一个默认值,有了默认值之后:调用函数时,使用了实参,用的就是实参的值调用函数时,没有使用实参,用的就是默认值;

2.因为函数传参的时候遵循靠左原则,所以在给定默认参数的时候要遵循靠右原则,否则会有歧义,报错;

3.函数的默认参数只能写在声明处,不能写在定义处。因为调用函数时,先看到的是函数的声明;

4.函数重载和默认参数同时出现时,会报错。

2. 哑元

在定义函数的时候,可以指定某个或某几个参数只有类型,没有参数名,这个类型只起到一个占位的作用。

哑元可以用于实际开发的过程中代码升级的操作:

如原本函数实现某个功能需要3个参数,升级后只需要2个参数即可,这是只需要修改函数的形参,将不需要的参数改成哑元,调用处都无需修改

注意:这种用法虽然支持,但是一般都不使用

必须使用哑元的场景:

自增、自减运算符重载的时候,会用到哑元作为占位。

例:int my_add(int x, int , int z)

3. 内联函数-inline

内联函数会建议编译器将函数在调用处展开,从而减少函数跳转的时间开销,执行效率更高,但是内联的代价就是可执行文件会变大。

定义内联函数:

在定义函数前,需要使用 inline 关键字修饰

定义内联函数的要求:

1.内联函数只能定义在.h中,不能定义在.cpp中

2.内联函数要求函数体比较小,逻辑比较单一

具体是否在调用处展开,由编译器决定,我们决定不了。

3.1 内联函数和带参宏的区别

1.宏定义是在预处理阶段完成替换的,内联函数是在编译阶段处理的

2.内联函数本质也是函数,有函数的属性,会对参数的类型做检查,而宏定义只是简单的无脑替换。 

4. 构造函数

4.1 功能

在类实例化对象的过程中,给成员申请空间,如分配内存、打开文件等操作,完成对成员的初始化。

4.2 格式

(1)构造函数与类同名

(2)构造函数没有返回值

(3)构造函数一般受public权限控制

4.3 调用时机

在类实例化对象的过程中,会自动调用构造函数。

注意:构造函数不能手动调用,并且根据在栈区和堆区的不同,调用时机不同:

栈区:

类名 对象名(构造函数的实参表);//调用构造函数

堆区:

类名 *指针名;                                             //这个过程不会调用构造函数

指针名 = new 类名(构造函数的实参表);    //这个过程才会调用构造函数

例:

class Student{
    private:
        string name;
        int *age;
    public:
        Student(string n, int a){
            cout << "我是构造函数,正在初始化" << n <<endl;
            name = n;
            age = new int(a);
        }
};
int main(){
    //栈区实例化对象
    Student s1("zhangsan", 10);//调用构造函数
    s1.show();

    //堆区实例化对象
    Student *p1;
    p1 = new Student("lisi", 20);//调用构造函数
    p1->show();

    //malloc不会调用构造函数
    Student *p2;
    p2 = (Student *)malloc(sizeof(Student));

    return 0;
}

4.4 构造函数支持重载

默认构造函数:

如果类型没有显性的定义构造函数,编译器会提供一个默认的构造函数,形参列表为void,函数体为空,用来给实例化对象的过程使用。

如果类中显性的定义了构造函数,那么编译器就不再提供默认的版本了,所以,在这种场景下,如果想要使用无参的构造函数,也需要显性手动定义。

4.5 构造函数的初始化列表

可以在定义构造函数的时候,使用冒号的方式引出构造函数的初始化列表。

格式:

类名(构造函数的形参表):成员1(初值1),成员2(初值2){ 构造函数的函数体; }

例:

class Student{
    private:
        string name;
        int *age;
    public:
        Student(string n, int a):name(n), age(new int(a)){
            cout << "我是有参构造函数,正在初始化" << n <<endl;
        }
};

4.5.1 必须使用初始化列表的场景

1) 当成员变量名和构造函数的形参名重名时--也可以使用this指针解决;

2) 类中有引用作为成员的时候;

3) 类中有const修饰的成员变量时;

4) 当类中有成员子对象(其他类的对象)时;

例:

class Student{
    private:
        string name;
        int age;
    public:
        Student(){cout << "Student 无参构造函数" <<endl;}
        Student(string n, int a):name(n), age(a){
            cout << "Student 有参构造函数" <<endl;
        }
        void show(){
            cout<<name<<" "<<age<<endl;
        }
};

class Teacher{
    private:
        string name;
        int age;
        Student stu;
    public:
        Teacher(){cout<<"Teacher 无参构造函数"<<endl;}
        //当类中有成员子对象时,需要在构造函数的初始化中调用成员子对象的构造函数
        //并传参完成对成员子对象的初始化,如果没有调用成员子对象的构造函数
        //默认会调用成员子对象的无参构造函数
        Teacher(string n1, int a1, string n2, int a2):\
                name(n1), age(a1), stu(n2, a2){//stu(n2, a2)表示调用Student类的有参构造并传参
            cout<<"Teacher 有参构造函数"<<endl;
            //name = n1;
            //age = a1;
            //stu.Student(n2, a2);//错误的 构造函数不能手动调用
        }
        void show(){
            cout<<name<<" "<<age<<" "<<endl;
            //cout<<stu.name<<" "<<stu.age<<endl;//错误的 name 和 age 是 Student类中的私有的
            stu.show();
        }
};

5. 析构函数

5.1 作用

在对象消亡的时候,用来做释放空间等善后工作的。

5.2 格式

~类名(void){}                     //析构函数是没有参数的 所以不能重载

5.3 调用时机

对象消亡时,自动调用。          (析构函数可以手动调用 但是一般不这样做)

栈区:声明周期结束时

堆区:手动调用delete时

5.4 默认析构函数

如果类中没有显性的定义析构函数,编译器会默认提供一个函数体为空的析构函数,用来消亡对象使用的,如果显性定义了,默认的版本就不提供了。

例:

class Student{
    private:
        string name;
        int *age;
    public:
        //类内定义的写法
        ~Student(void){
            cout<<"析构函数"<<endl;
            if(age != NULL){
                delete age;
                age = NULL;
            }
        }
};

5.5 构造函数和析构函数调用的顺序

1.对于堆区的对象:

先new哪个对象,就先构造哪个对象,先delete哪个对象,就先析构哪个对象,所以,一般不考虑堆区的构造函数和析构函数的调用顺序

2.对于栈区的对象:

构造函数的调用顺序:按顺序调用

析构函数的调用顺序:逆序调用

----先构造的后析构,栈的顺序

6. 拷贝构造函数

6.1 格式

函数名:与类同名

返回值:没有返回值

形参: const 类名 &

类名(const 类名 &other){

}

6.2 调用时机

用一个已经初始化的类对象,去初始化新对象时,会自动调用拷贝构造函数

类名 对象1(构造函数的实参表);                 // 有参构造函数

类名 对象2(对象1);                                    //拷贝构造函数

类名 对象3 = 对象1;                                  //拷贝构造函数

类名 *指针名 = new 类名(对象3);           //拷贝构造函数

类名 对象2(对象1);

6.3 C++中浅拷贝和深拷贝的区别

浅拷贝:

如果类中没有显性的定义拷贝构造函数,编译器会提供一个默认的拷贝构造函数,这个默认的拷贝构造函数,只完成成员之间的简单赋值,如果类中没有指针成员,只用这个默认的拷贝构造函数,是没有问题的。

深拷贝:

如果类中有指针成员,并且使用浅拷贝,指针成员之间也是只做了简单的赋值,相当于两个对象的指针成员指向的是同一块内存空间,调用析构函数的时候,就会出现 double free 的问题,此时,需要在类中显性的定义拷贝构造函数,并且,给新对象的指针成员分配空间,再将旧对象的指针成员指向的空间里的值拷贝一份过来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值