第五章 析构函数、命名空间、继承、成员访问权限、初始化列表

析构函数(Destructor)

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

     struct Person
     {
     	int m_age;
     	//对象创建完毕的时候调用
     	Person() {
     		cout << "Person()" << endl;
     		this->m_age = 0;
     	}
     	//对象被销毁的时候调用
     	~Person() {
     		cout << "~Person()" << endl;
     	}
     };
    
     int main() {
     	//栈
     	{
     		Person person;
     	}
     	//堆
     	Person *p = new Person;
     	delete p;
        
     	getchar();
     	return 0;
     }
    

对象的内存管理

  1. 对象内部申请的堆空间,由对象内部回收
  2. 多注意setter和析构的内存管理
    1. 注意点1:类类型的成员函数是指针与是类的区别

       struct Car
       {
           	Car() {
           		cout << "car()" << endl;
           	}
           	~Car(){
           		cout << "~Car()" << endl;
           	}
              
       };
              
       struct Person
       {
           	int m_age;
           	//Car m_car;//这么写,在创建person对象的时候会创建一个Car对象
           	Car *m_car;//这么写,在创建person对象的时候只会创建一个指针变量,占4个字节,不会创建Car对象
           	Person() {
           		cout << "Person()" << endl;
           	}
                  
           	//内存回收、清理工作
           	~Person() {
           		cout << "~Person()" << endl;
           	}
       };
              
       int main(){
           	Person *p = new Person;
             delete p;
           	getchar();
           	return 0;
       }
      
    2. 在构造函数内部分配内存,需要在析构函数内部释放

       struct Car
       {
           	int m_price;
           	Car() {
           		cout << "car()" << endl;
           	}
           	~Car(){
           		cout << "~Car()" << endl;
           	}
       };
              
       struct Person
       {
           	int m_age;
           	Car *m_car;
           	Person() {
           		cout << "Person()" << endl;
           		this->m_car = new Car();
           	}
                  
           	//内存回收、清理工作
           	~Person() {
           		cout << "~Person()" << endl;
           		delete this->m_car;
             }
       };
              
       //使用
       Person *p = new Person();
       delete p;
      
    3. 在setter方法内部分配内存,同时在setter方法内部释放,但是调用释放在析构函数中

       struct Person
       {
       private:
       	char *m_name;
       public:
       	void setName( const char *name) {
       	   //重复设置,返回
       		if (this->m_name == name) return;
       		//释放旧空间
       		if (this->m_name != NULL){
       			delete[] this->m_name;
       			this->m_name = NULL;
       		}
       		//开辟新空间
       		if (name != NULL){
       		  //为何+1,因为字符串默认有个\0字符,占一个字节,strlen只计算的是不带\0的长度
       		  this->m_name = new char[strlen(name) + 1]();
       		  strcpy(this->m_name, name);
       		}
       	}
       	const  char *getName() {
       		return this->m_name;
       	}
       	Person() {
       		memset(this, 0, sizeof(Person));
       	}
       	~Person(){
       		this->setName(NULL);
       	}
       };
      

声明和实现分离

  1. 类的声明写到.h文件,类的实现写到.cpp;当然也可以都写在一个文件中

     //Person.h
        
     #pragma once
     class Person
     {
     public:
     	Person();
     	~Person();
     	void setAge(int age);
     	int getAge();
        
     private:
     	int m_age;
     }
        
     //Person.cpp
     #include<iostream>
     #include"Person.h"
     using namespace std;
        
     Person::Person(){
     	cout << "person()" << endl;
     }
        
     Person::~Person(){
     	cout << "~Person()" << endl;
     }
        
     void Person::setAge(int age) {
     	this->m_age = age;
     	cout << "setAge" << endl;
     }
        
     int  Person::getAge() {
     	cout << "getAge" << endl;
     	return this->m_age;
     }
        
     int main() {
     	Person person;
     	person.setAge(10);
     	cout << person.getAge() << endl;
     	getchar();
     	return 0;
     }
    

命名空间

  1. 命名空间可以用来避免命名冲突

     class Person
     {
     public:
     	int m_age;
     };
     //全局变量
     int g_no;
        
     //函数
     void test() {
        
     }
        
     //命名空间
     namespace ZH {
     	//全局变量
     	int g_no;
     	//函数
     	void test() {
        
     	}
        
     	class Person
     	{	
     	public:
     		int m_age;
     	};
     }
        
     int main() {
    
     	Person person;
     	person.m_age = 10;
        
     	ZH::Person person2;
     	person2.m_age = 20;
        
     	g_no = 30;
     	ZH::g_no = 40;
        
     	test();
     	ZH::test();
        	
     	cout << "person.m_age=" << person.m_age << endl;
     	cout << "person2.m_age=" << person2.m_age << endl;
        
     	getchar();
     	return 0;
     }
    
  2. using namespace 空间名;
    1. 声明我要用“空间名”中的东西,统一声明,不用在使用的时候还要每次写
     //命名空间
     namespace ZH {
     	//全局变量
     	int g_no;
     	//函数
     	void test() {
        
     	}
        
     	class Person
     	{	
     	public:
     		int m_age;
     	};
     }
        
     //统一使用
     using namespace ZH;
     g_no = 10;
     Person person;
     test();
        
     //指定用命名空间的哪一个
     using ZH::g_no;
     g_no = 10;
    
  3. 命名空间不影响内存布局

命名空间的嵌套

  1. 举例:

     namespace ZH {
     	 namespace SS {
     		 int g_age;
     	 }
     }
        
     int main() {
        
     	ZH::SS::g_age = 10;
        
     	using namespace ZH::SS;
     	g_age = 20;
        
     	using ZH::SS::g_age;
     	g_age = 30;
     }
    
  2. 有个默认的全局命名空间,我们创建的命名空间默认都嵌套在它里面

     int g_no;
    
      namespace ZH {
     	 namespace SS {
     		 int g_age;
     	 }
     }
        
     int main() {
     	//下面2个等价
     	g_no = 10;
     	//默认命名空间::,全局命名空间
     	::g_no = 10;
        
     	using namespace ZH::SS;
     	g_age = 20;
     }
    

命名空间的合并

  1. 以下2种写法是等价的

     //写法1
     namespace ZH {
     	int g_age;
     }
        
     namespace ZH {
     	int g_no;
     }
        
     //等价于写法2
     namespace ZH {
      int g_age;
      int g_no;
     }
    
  2. 命名空间用在类中

     //Person.h
     #pragma once
     namespace ZH {
     	class Person
     	{
     	public:
     		Person();
     		~Person();
     	};
     }
        
     //Person.cpp
     #include "Person.h"
    
     namespace ZH {
     	Person::Person(){
     	}
        
        
     	Person::~Person(){
     	}
     }
        
     //main函数使用
     #include<iostream>
     #include"Person.h"
     using namespace ZH;
     using namespace std;
        
     int main() {
         Person person;
         return 0;
     }
    

继承

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

     struct Person
     {
     	int m_age;
     	void run() {
     		cout << "run()" << endl;
     	}
     };
        
     //继承Person
     struct Student : Person
     {	
     	int m_no;
     	void study() {
     		cout << "study()" << endl;
     	}
     };
        
     int main() {
        
     	Student student;
     	student.m_age = 10;
     	student.m_no = 22;
     	student.run();
     	student.study();
        
     	getchar();
     	return 0;
     }
    
    1. 关系描述
      1. Student是子类(subclass,派生类)
      2. Person是父类(superclass,超类)
    2. C++中没有像Java、Objective-C的基类
      1. Java:java.lang.Object
      2. Objective-C:NSObject

对象的内存布局

图1

成员访问权限

  1. 成员:成员变量、成员函数
  2. 成员访问权限、继承方式有3种
    1. public:公共的,任何地方都可以访问(struct默认)
    2. protected:子类内部、当前类内部可以访问
    3. prvate:私有的,只有当前类内部可以访问(class默认)
     struct Person
     {
     //public:
     protected:
     //private:
     	int m_age;
     	void run() {
     		cout << "run()" << endl;
     		//当前类的成员由public或者protected或者private修饰,在当前类内部可以访问成员。
     		this->m_age = 0;
     	}
     };
     struct Student : Person
     {	
     	int m_no;
     	void study() {
     		//当父类的成员为public或者protected时,子类可以访问父类的成员
     		//如果父类成员为private,子类不可以访问
     		this->m_age = 20;
     		this->run();
     	}
     };
        
     int main() {
     	Person person;
     	//当前类的成员被protected或private修饰时,外部不可以访问。
     	person.m_age = 10;
        
     	//当父类的子成员被修饰为protected或private,外部不可以访问。
     	Student student;
     	student.m_age = 10;
        
     	getchar();
     	return 0;
     }
    
  3. 从上面说明继承也有三种方式public、protected、private
    1. class创建的类默认继承方式是private
    2. struct创建的类默认继承方式是public
  4. 子类内部访问父类成员的权限,是以下2项中权限最小的那个
    1. 2项指:
      1. 成员本身的访问权限
      2. 上一级父类的继承方式
    2. 即:2项指同时有成员权限、继承权限

       struct Person
       {
       public:
       	int m_age;
       	void run() {
       		cout << "run()" << endl;
       		this->m_age = 0;
       	}
       };
       struct Student : private Person
       {	
       	int m_no;
       	void study() {
       	   //但是子类内部仍然可以访问父类的成员!!!
       	   this->m_age = 20;
       		this->run();
       	}
       };
       struct GoodStudent :Student
       {
       	int m_money;
       	void test() {
       		//不能调用
       		//this->m_age = 30;
       		//this->run();
       		this->study();
       		this->m_no = 55;
       	}
       };
       //尽管父类的成员是public,但是有与子类继承是private,那么二者取最小权限private
       Student student;
       //不能访问
       //student.m_age = 10;
       //可以访问
       student.study();
       student.m_no = 66;
      
      1. 从上面的3个类中我们可以看出
        1. Person成员为public
        2. Student的继承方式为private,但是
          1. Student内部可以访问父类Person的成员
          2. 外部不能通过Student访问其父类成员
          3. 外部可以通过Student 访问当前类的成员,说明当前类的权限仍然是默认的public
        3. GoodStudent的内部不能访问Person的成员
        4. 结论:继承权限本质就是将父类的成员拿到子类,并全部把父类的成员权限设置为同继承权限一样的权限,但是不会影响当前类的成员权限
        5. 上面整合成一个类就是如下:

           //本质
           struct GoodStudent :Student
           {
           //Person的成员,因为为private继承
           private:
               int m_age;
               void run() {
                   cout << "run()" << endl;
                   this->m_age = 0;
               }
                              
           public:
               //Student的成员,因为Student为Struct创建,因此默认为public继承
               int m_no;
               void study() {
                   //但是子类内部仍然可以访问父类的成员
                   this->m_age = 20;
                   this->run();
               }
                              
               //CoodStudent自己的成员
               int m_money;
               void test() {
                   //不能调用
                   //this->m_age = 30;
                   //this->run();
                   this->study();
                   this->m_no = 55;
               }
           };
          
  5. 开发中用的最多的继承方式是public,这样能保留父类原来的成员访问权限
  6. 访问权限不影响对象的内存布局

初始化列表

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

     struct Person
     {
     	int m_age;
     	int m_height;
     	Person() {
     		this->m_age = 0;
     		this->m_height = 0;
     	}
     	//初始化列表
     	Person(int age, int height) : m_age(age),m_height(height) {
     	}
     	//这么写仍然是先赋值age,只跟成员变量声明顺序有关
     	//Person(int age, int height) :m_height(height),m_age(age){}
        	
     	//由反汇编看出:这么写等价于初始化列表
     	/*Person(int age, int height) {
     		this->m_age = age;
     		this->m_height = height;
     	}*/
     	void display() {
        
     		cout << "age is " << this->m_age << endl;
     		cout << "height is " << this->m_height << endl;
     	}
     };
     Person person1;
     person1.display();
     Person person(10,20);
     person.display();
    

构造函数的互相调用

  1. 案例:

     struct Person
     {
     	int m_age;
     	int m_height;
     	Person() {
     		cout << "Person()" << endl;
     		//直接调用构造函数,这个不能初始化成功!!!
     		//因为这就相当于又重新创建了一个临时对象 ,等价于Person tem(0,0);这是一个新的对象
     		Person(0, 0);
     	}
     	Person(int age, int height) {
     		cout << "Person(int,int)" << endl;
     		this->m_age = age;
     		this->m_height = height;
     	}
    
     	void display() {
        
     		cout << "age is " << this->m_age << endl;
     		cout << "height is " << this->m_height << endl;
     	}
     };
     int main() {
        
     	Person person;
     	person.display();
     }
        
     //打印结果
     Person()
     Person(int, int)
     age is - 858993460
     height is - 858993460
    
    1. 可以看见Person()、 Person(int, int)都调用了,那为什么没有初始化成功呢?(可以由反汇编分析)
      1. 构造函数是对象被创建完毕后调用的,即先分配对象内存,然后调用构造函数
      2. 调用构造函数时,会将当前对象的地址传递给Person()内部的this指针
      3. 构造函数内部调用构造函数Person(0,0);这就相当于又重新创建了一个临时对象,等价于 Person tem(0,0);
      4. 调用构造函数Person(0,0)时,传递给Person(0,0)构造函数内部的this指针是那个临时对象的指针(tem);
      5. 那么Person(0,0)构造函数内部的this指针指向的可不是原来对象的地址了,而是Person()内部通过Person(0,0)创建的临时对象的地址值,并不是Person()内部this指针指向的对象地址
      6. 因此,Person(0,0)内部使用this赋值肯定不是原对象的值
  2. 结论:构造函数调用构造函数必须在初始化列表中调用

    1. 把上面的构造函数Person(),修改为如下就可以调用成功了
     Person() : Person(0,0){
         cout << "Person()" << endl;
     }
    
    1. 这么调用就会把Person()内部的this指针指向的地址值(原对象的地址)传递给Person(int,int)
    2. 那么Person(int,int)内部的this指针指向的就是原来的对象了,就可以初始化成功了。

初始化列表与默认参数配合使用

  1. 举例

     class Person {
     	int m_age;
     	int m_height;
     public:
     	Person(int age = 0,int height = 0):m_age(age),m_height(height){
        
     	}
     };
        
     //使用
     Person perosn1;
     Person person2(18);
     Person person3(18, 190);
    
  2. 如果函数声明和实现是分离的

    1. 初始化列表只能写在函数的实现中
    2. 默认参数只能写在函数的声明中(前面讲过)
     class Person {
     	int m_age;
     	int m_height;
     public:
     	//默认参数只能写在声明中
     	Person(int age = 0, int height = 0);
     };
     //构造函数的初始化列表只能写在实现中
     Person::Person(int age, int height) :m_age(age), m_height(height){
        
     }
        
     //使用
     //注意:这个为何不报错??? 前面讲到,一旦手动实现构造函数,而且没有实现不带参数的构造函数,这么创建就会报错,说明这里一定产生了默认无参的构造函数,那么这就是特殊情况下???---不是,这里调用的不是无参的构造函数,而是等价于 Person(0,0);
     Person perosn1; //等价于Person(0,0);而不是Person();
     Person person2(18);
     Person person3(18, 190);
    

父类的构造函数

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

     class Person {
     	int m_age;
     public:
     	Person() {
     		cout << "Person()" << endl;
     	}
     	Person(int age) : m_age(age) {
     		cout << "Person( int)" << endl;
     	}
     };
        
     class Student :Person {
     	int m_score;
     public:
     	Student() {
     		cout << "Student()" << endl;
     	}
        	
     	Student(int age, int score) :m_score(score),Person(age) {
     		//不能这么调用,否则就是创建一个临时Person对象了,不是原来的对象了
     		//Person(age)
     		cout << "Student( int ,int )" << endl;
     	}
     	Student(int age) :m_score(m_score) {
        
     		cout << "Student( int )" << endl;
     	}
     };
     //除了调用当前类的无参构造函数,而且会先调用父类的无参构造函数
     Student student;
     //不会调用父类无参的构造函数
     Student student2(18, 180);
     //还是会调用父类无参的构造函数
     Student student3(120);
    
  3. 如果父类缺少无参构造函数,子类的构造函数必须显式调用父类的有参构造函数

     class Person {
     	int m_age;
     public:	
     	Person(int age) : m_age(age) {
     		cout << "Person( int)" << endl;
     	}
     };
        
     class Student :Person {
     	int m_score;
     public:
     	//根本不让你实现,因为父类没有无参构造函数
     	/*Student() {
     		cout << "Student()" << endl;
     	}
     	*/
     	/*Student(int age) :m_score(m_score) {
        
     		cout << "Student( int )" << endl;
     	}*/
        
     	//子类的构造函数必须显式调用父类的有参构造函数
     	Student(int age, int score) :m_score(score),Person(age) {
     		//不能这么调用,否则就是创建一个临时Person对象了,不是原来的对象了
     		//Person(age)
     		cout << "Student( int ,int )" << endl;
     	}
     };
        
     //使用
     Student student(18, 99);
    

继承体系下的构造函数相互调用示例

class Person {
	int m_age;
public:
	
	Person():Person(0) {
	}
	Person(int age) : m_age(age) {
	}
};

class Student :Person {
	int m_score;
public:
	Student() :Student(0,0) {
		cout << "Student()" << endl;
	}
	Student(int age, int score) :m_score(score),Person(age) {
		//不能这么调用,否则就是创建一个临时Person对象了,不是原来的对象了
		//Person(age)
	}
};

构造、析构顺序

  1. 构造和析构的顺序相反

     struct Person
     {
     	Person() {
     		cout << "Person()" << endl;
     	}
     	~Person(){
     		cout << "~Person()" << endl;
     	}
     };
        
     struct Student :Person
     {
     	Student() {
     		cout << "Student()" << endl;
     	}
     	~Student(){
     		cout << "~Student()" << endl;
     	}
     };
        
     //调用
     Student *p = new Student;
     delete p;
        
     //打印
     Person()
     Student()
     ~Student()
     ~Person()
    
Table of Contents