C++知识点总结(面向对象2-构造函数, 初始化列表)

构造函数1(Constructor)

构造函数(也叫构造器), 在对象创建的时候自动调用, 一般用于完成对象的初始化工作.
特点:
(1)函数名与类同名, 无返回值(void都不能写), 可以有参数, 可以重载, 可以有多个构造函数.
(2)一旦自定义了构造函数, 必须用其中一个自定义的构造函数来初始化对象.
注意:
(1)通过malloc分配的对象不会调用构造函数
(2)通过new分配的对象可以调用构造函数, 即在堆空间中的对象可以调用构造函数, 在栈空间的也可以, 在全局区(数据段)的也可以.只有malloc不会调用.因为malloc在C语言就有了, 那时候还没有构造函数.
所以new至少比malloc多做了调用构造函数这件事情.
(3)一个广为流传的, 很多教程\书籍都推崇的错误结论:
默认情况下, 编译器会为每一个类生成空的无参的构造函数.
正确理解: 在某些特定的情况下, 编译器才会为类生成空的无参的构造函数.

构造函数2

构造函数的调用:

#include <iostream>
using namespace std;

class Person {
public:
    int m_age;

    Person() {
        m_age = 0;
        cout << "Person()" << endl;
    }

    Person(int age) {
        m_age = age;
        cout << "Person(int age)" << endl;
    }
};

Person person1(); // 函数声明

Person person1() { // 函数实现
    return Person();
}

Person g_person0; // Person();
Person g_person1(); // 全局的函数声明
// 返回值  函数名
Person g_person2(10); // Person(int)

int main()
{
    Person person0; // Person()
    Person person1(); // 局部的函数声明
    Person person2(20); // Person(int)

    Person *p0 = new Person; // Person()
    Person *p1 = new Person(); // Person()
    Person *p2 = new Person(30); // personn(int)
    return 0;
}

成员变量的初始化(了解)

如果自定义了构造函数, 除了全局区, 其他内存空间的成员变量默认都不会被初始化, 需要开发人员手动初始化.
如果有很多个成员变量, 如何方便的初始化.

class Person {
	int m_age1;
	int m_age2;
	int m_age3;
	int m_age4;
	int m_age5;
	Person() {
		memset(this, 0, sizeof(Person));
		// 从person对象的地址开始, 它的所有字节都清0
	}
};

析构函数(Destructor)

析构函数(也叫析构器), 在对象销毁(对象的内存被回收的时候)的时候自动调用, 一般用于完成对象的清理工作.
特点:
(1)函数名以~开头, 与类同名, 无返回值(void都不能写), 无参, 不可以重载, 有且仅有一个析构函数.
注意:
(1)通过malloc分配的对象free的时候不会调用析构函数.
(2)构造函数, 析构函数要声明为public, 才能被外界正常使用.所以构造函数和析构函数必须声明为public, 否则会报错.

继承

继承, 可以让子类拥有父类的所有成员(变量\函数)

成员访问权限

成员访问权限, 继承方式有3种
public : 公共的, 任何地方都可以访问(struct默认)
protected: 子类内部, 当前类内部可以访问
private: 私有的, 只有当前类内部可以访问(class默认)
结论:
子类内部访问父类成员的权限, 是以下2项中权限最小的那个
1.成员本身的访问权限
2.上一级父类的继承方式
(1)所以一般都写public继承, 因为public继承可以将父类原有的访问权限继承下来.
(2)访问权限不影响对象的内存布局.

初始化列表1

特点:
(1)一种便捷的初始化成员变量的方式
(2)只能用在构造函数中
(3)初始化顺序只跟成员变量的声明顺序有关

#include <iostream>
using namespace std;

class Person {
public:
    int m_age;
    int m_height;

    /*Person(int age, int height) {
        m_age = age;
        m_height = height;
    }*/
    // 语法糖
    Person(int age, int height) : m_age(age), m_height(height) {

    }
    // 这个构造函数与上面的构造函数等价
};

int main()
{
    Person person(18, 180);   
    cout << person.m_age << endl;
    cout << person.m_height << endl;
    return 0;
}

初始化列表与默认参数配合使用
#include <iostream>
using namespace std;

class Person {
public:
    int m_age;
    int m_height;
    Person(int age = 0, int height = 0) : m_age(age), m_height(height) {

    }
    // 一个构造函数相当于写了3个构造函数
};

int main()
{
    Person person1;
    Person person2(19);
    Person person3(20, 180);
    return 0;
}


如果函数声明和实现是分离的
初始化列表只能写在函数的实现中
默认参数只能写在函数声明中

#include <iostream>
using namespace std;

class Person {
public:
    int m_age;
    int m_height;

    Person(int age = 0, int height = 0);
};

Person::Person(int age, int height) : m_age(age), m_height(height) {
    
}

int main()
{
    Person person;
    return 0;
}


构造函数的互相调用

结论:构造函数调用构造函数要在初始化列表里面写.
例如:

#include <iostream>
using namespace std;

class Person {
public:
   int m_age;
   int m_height;

   Person() : Person(0, 0) {}
   Person(int age, int height) : m_age(age), m_height(height) {}
};

int main()
{
   Person person; 
   return 0;
}


注意:下面的写法是错误的, 初始化的是一个临时对象.

class Person {
   int m_age;
   int m_height;
   Person() {
   	Person(0, 0);
   }
   Person(int age, int height) : m_age(age), m_height(height)  {}
};

初始化列表3-父类的构造函数

(1)子类的构造函数默认会调用父类的无参构造函数
(2)如果子类的构造函数显式地调用了父类的有参构造函数, 就不会再去默认调用父类的无参构造函数

#include <iostream>
using namespace std;

class Person {
public:
    int m_age;

    Person() {
        cout << "Person::Person()" << endl;
    }

    Person(int age) {
        cout << "Person::Person(int age)" << endl;
    }
};

class Student : public Person {
public:
    int m_no;
    
    Student() : Person(10) {
        cout << "Student::Student()" << endl;
    }
};

int main()
{
    Student student;

    return 0;
}

(3)如果父类缺少无参构造函数(但有有参构造函数), 子类的构造函数必须显式调用父类的有参构造函数.不然会报错. 因为子类的构造函数默认会调用父类无参的构造函数, 如果父类缺少无参的构造函数, 就报错.

#include <iostream>
using namespace std;

class Person {
public:
    int m_age;

    Person(int age) {
        cout << "Person::Person(int age)" << endl;
    }
};

class Student : public Person {
public:
    int m_no;

    Student() : Person(10) {
        cout << "Student::Student()" << endl;
    }
    //父类缺少无参的构造函数, 子类的构造函数
    //必须显式的调用父类的有参构造函数.
};

int main()
{
    Student student;
    return 0;
}


(4)如果父类什么构造函数都没有, 那子类的构造函数就不调用了.
(5)价值:

#include <iostream>
using namespace std;

class Person {
private:
    int m_age;

public:
    Person(int age) : m_age(age){  }
    int getAge() {
        return m_age;
    }
};

class Student : public Person {
private:
    int m_no;

public:
    Student(int age, int no) : Person(age), m_no(no) {  }
    int getNo() {
        return m_no;
    }
};

int main()
{
    Student student(10, 20);
    cout << student.getAge() << '\n' <<
        student.getNo() << endl;
    return 0;
}


其他C++系列文章:

C++知识点总结(基础语法1-函数重载, 默认参数)
C++知识点总结(基础语法2-内联函数, const, 引用)
C++知识点总结(面向对象1-类和对象, this指针, 内存布局)
C++知识点总结(面向对象2-构造函数, 初始化列表)

C++知识点总结(面向对象3-多态)

C++知识点总结(面向对象4-多继承, 静态成员static)
C++知识点总结(面向对象5-const成员, 拷贝构造函数)
C++知识点总结(面向对象6-隐式构造, 友元, 内部类, 局部类)
C++知识点总结(其他语法1-运算符重载)
C++知识点总结(其他语法2-模板, 类型转换, C++11新特性)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值