C++学习笔记(四)

构造函数和析构函数

c 复制代码
构造函数的语法形式:
-------------------------------
class 类名
{
    public: 

        类名(构造函数的形参列表)
        {
            //构造的函数中的逻辑:就是对类对象中属性进行初始化的。        
        }
};
-----------------------------------------
构造的意义:就是用来对类的对象的属性进行初始化的。
-------------------------------------------------------------
    //C++中的如果类中没有手动提供任何一种构造,

	在定义对象时,g++编译器会自动为这个类型生成一个默认的无参空构造。

    //如果在这个类中已经提供了任何一种构造,那么g++编译器将不再提供。

    //推荐大家在构建类型时,手动提供一个默认无参的空构造。
--------------------------------------------------------------------------
    Rect()
    {
        memset(this,0,sizeof(Rect)) 
        cout << "默认无参的空构造" << endl;
    }
    Rect(int width, int height)
    {
        _width = width;
        _height = height;
        cout << "有参构造" << endl;
    }
------------------------------------------------------
    Rect r1;
    Rect r2;
    //r1.Rect(10,20);
    Rect r3(10,20);
--------------------------------
构造函数的调用时机:
----------------------------
定义对象时,由编译器自动调用:
1.在栈上定义的对象
2.在堆上定义的对象(手动在堆上申请内存后)
-----------------------------------------------------
构造的调用形式:显式调用,隐式调用:
---------------------------------------------
    Rect()
    {
        _width = 0;
        _height = 0;
        cout << "默认无参的空构造" << endl;
    }

    //explicit 强制编译使用显式的调用构造。

    explicit Rect(int width, int height)
    {
        _width = width;
        _height = height;
        cout << "双参构造" << endl;
    }
    explicit Rect(int size)
    {
        cout << "Rect的单参构造" << endl;
    }
----------------------------------------------------------
析构函数的语法形式:
------------------------------
class 类名
{
public:

    ~类名()

    {
        //释放类中指针属性指向堆上资源的逻辑:   
    }

};
-------------------------------------
重点:析构函数调用时机:
--------------------------------
在类对象被销毁前,由编译器自动调用。

完成对类对象的属性指针指向堆上资源的清理。
----------------------------------------------------------
如果封装的类型中有指针属性时,

一定要在析构函数中书写回收这个属性指针所指向堆资源的逻辑。
-----------------------------------------------------
class Person
{
private:
    string _name;
    int _age;
    int* _p;
public:
    Person()
    {
        memset(this,0,sizeof(Person));
        cout << "Person的无参空构造" << endl;
    }


    Person(string name, int age)
    {
        _name = name;
        _age = age;
        _p = new int[1024]();
        cout << "Person的有参构造" << endl;
    }

    //析构函数的调用时机:在对象被销毁前那一刻自动调用。

    ~Person()
    {

        //回收类对象中的指针属性所指向堆上资源。

        if(_p != nullptr)
        {
            delete [] _p;
            _p = nullptr;
        }
        cout << "Person的析构" << endl;
    }
    void showInfo()
    {
        cout << "姓名:" << _name << ",年龄:" << _age << endl;
    }
};

void test()
{
    Person p1;
    Person p2("zhangsan",18);
    p2.showInfo();
    Person* ptr = new Person("lisi",20);
    delete ptr;
}
-------------------------------------------------
构造函数的特殊语法:初始化列表:
---------------------------------------
#include <iostream>
#include <cstring>
using namespace std;
class A
{
public:
    A(int a)
    {


    }
};


class Person
{
private:
    string _name;
    int _age;
    int* _p;
    //类中只读的特殊属性的初始化,请在初始化列表中进行初始化。
    const int _id;
    A a;


public:
    //构造函数的特殊语法:构造函数的初始化列表:
    Person():_id(0),a(A(0))
    {
        memset(this,0,sizeof(Person));
        cout << "Person的无参空构造" << endl;
    }


    Person(string name, int age,int id)
        //初始列表中初始化时,请遵循按照类中属性声明的顺序依次进行初始化。
        :_name(name),_age(age),_p(new int[1024]()),_id(id),a(A(age))
    {


        cout << "Person的有参构造" << endl;
    }


    //析构函数的调用时机:在对象被销毁前那一刻自动调用。
    ~Person()
    {
        //回收类对象中的指针属性所指向堆上资源。
        if(_p != nullptr)
        {
            delete [] _p;
            _p = nullptr;
        }
        cout << "Person的析构" << endl;


    }


    void showInfo()
    {
        cout << "姓名:" << _name << ",年龄:" << _age << ",id:" << _id << endl;
    }
};


void test()
{
    Person p2("zhangsan",18,1001);
    p2.showInfo();


    Person* ptr = new Person("lisi",20,1002);
    ptr->showInfo();
    delete ptr;
}


int main()
{
    test();
    //其它的操作:
    return 0;
}
-------------------------------------------------------------------
初始化列表的调用时机:
--------------------------------
在类开辟空间时,同时会被调用,

就是告诉编译器按类中成员的声明顺序进行初始化,

是在构造函数调用前的。
---------------------------------------
所以初始化列表的特点有以下几点:
--------------------------------------------
1.每个成员变量在初始化列表中,只能初始化一次。

2.类中有特殊成员时的初始化:const修饰的对象或变量,
引用类型成员或变量,自定义类对象没有默认构造的指定调用构造函数时使用。

3.如果类中有比较大的结构体对象的初始化,可以在初始化列表中进行。

4.初始化列表顺序尽量保持与类中成员声明顺序相同。

常引用和常函数

c 复制代码
常引用
-----------
用const声明的引用就是常引用。常引用所引用的对象不能被更改。

经常见到的是常引用作为函数的形参,这样不会发生对实参的误修改。

常引用的声明形式为:const 类型说明符 &引用名。
----------------------------------------------------------


常对象
----------------
常对象是指数据成员在它的生存期内不会被改变。

定义常对象时必须对其进行初始化,并且不能改变其数据成员的值。

常对象的声明形式为:类名 const 对象名 或者 const 类名 对象名。
-----------------------------------------------------------------------------------


常函数
-------------------
类中用const声明的成员函数就是常成员函数,常成员函数的声明形式为:

类型说明符 函数名(参数表) const;。
-----------------------------------------------

常成员函数需要注意的几点:

常成员函数在声明和实现时都要带const关键字;

常成员函数不能修改对象的数据成员,

也不能访问类中没有用const声明的非常成员函数;


常对象只能调用它的常成员函数,不能调用其他的普通成员函数;

-------------------------------------------------------
const关键字可以被用于参与对重载函数的区分。

比如,如果有两个这样声明的函数:void fun(); void fun() const;,

则它们是重载函数。




常数据成员
--------------------
类的数据成员也可以是常量和常引用,

用const声明的数据成员就是常数据成员。
-----------------------------------------------

在任何函数中都不能对常数据成员赋值。

构造函数对常数据成员初始化,只能通过初始化列表。




常对象与常函数(常方法):
-------------------------------
常对象:
------------------------
由const修饰的类型对象或常引用类型及常指针类型
-------------------------------------------------------------------------
常对象的特性:
----------------------
1.不可以修饰改本对象中相应的属性。

2.不可以调用类中的普通方法。
常对象只能调用常方法。

普通对象不仅可调用普通方法,也可调用常方法。
---------------------------------------------------------
常方法:
---------------------
类中的成员函数的函数体由const修饰,
其本质是修饰的成员函数隐藏的this指针类型:
常方法函数体中不可能出现有修改类对象属性的逻辑出现。
------------------------------------------------------------------
常方法的语法形式:
--------------------------
class 类名
{
    成员函数(形参列表)const
    {
        //在这个常方法函数体中不可以出现修改类对象中属性的逻辑。
        //可以读取,但不可以修改。    
    }
};

虚函数和纯虚函数

c 复制代码
纯虚函数的概念
----------------------------
纯虚函数是一种在父类中只有声明,没有定义的函数。


声明纯虚函数的方式
------------------------------
在虚函数的基础上,把{}和函数体都去掉,用一个 =0 来替换,
那么原来的虚函数就变成了纯虚函数。

使用纯虚函数的要求
--------------------------------
由于父类中没有纯虚函数的定义,只有声明,所以要求:子类中必须重写父类的纯虚函数。

抽象类
---------------
包含纯虚函数的类,就叫抽象类。


笔试面试题:
-------------------------
函数重载和函数重写的区别?
------------------------------------------
函数重载:要求,函数名相同,形参列表必须不同。
函数重写:要求,函数原型必须都相同,函数重写只发生在父子类中间。


C++中在声明和定义函数时,在函数前面加上关键字 virtual修饰,那么这个函数就叫做虚函数。


虚函数:
----------------------
类中的成员函数前加virtual进行修饰就是一个类中的虚函数。

注意:构造函数不可以使用virtual进行修饰。

虚函数的特性:类中的虚函数具有虚属性,
也就说函数逻辑可以在子类之中完成重写。

重写概念:即是类似于在子类之中对父类中同名虚函数的重新定义
(函数逻辑的不同实现)
----------------------------
虚函数的语法形式:
----------------------------
class 类名:
{
    //访问权限:

    virtual + 返回值 + 函数名(形参列表)
    {
        //虚函数的函数体,函数体具有虚属性,就是可以在子类写成重新定义。                
    }
};

虚析构和纯虚析构

c 复制代码
虚析构 :
-----------------------
若子类中存在指向堆区的属性,
须利用虚析构技术(将父类析构函数写成虚函数),
在delete时,才会调用子类的析构函数。
--------------------------------------------------------
#include <iostream>
#include <string>
using namespace std;
 
class Animal
{
public:
    Animal()
	{
		cout<<"Animal默认构造函数调用"<<endl;
	}
    
    // 虚析构函数
    // 若子类中存在指向堆区的属性,须利用虚析构技术(将父类析构函数写成虚函数),在delete时,才能调用子类的析构函数。
    virtual ~Animal()
    {
	    cout<<"Animal析构函数调用"<<endl;
    }
 
    virtual void speak()
	{
		cout<<"动物在说话"<<endl;
	}
};
 
class Cat: public Animal
{
private:
    char* name;
public:	
	Cat(const char* name)
    {
		cout<<"Cat有参构造函数调用"<<endl;
		this->name = new char[strlen(name)+1];
		strcpy(this->name, name);
	}
	
	~Cat()
    {
		cout<<"Cat析构函数调用"<<endl;
		if(NULL!=this->name)
		{
			delete[] this->name;
			this->name = NULL;
		}
	}
    
	void speak()
	{
		cout<<this->name<<" 小猫在说话"<<endl;
	}
};
 
void doSpeak(Animal* animal)
{
	animal->speak();
	delete animal;
	animal = NULL;
}
 
int main()
{
	doSpeak(new Cat("小黑"));
	return 0;
}
-----------------------------------------
纯虚析构:
------------------
既要有声明,也须有定义;类内声明,类外定义。

--------------------------
#include <iostream>
#include <string>
using namespace std;
 
// 存在纯虚析构和虚析构函数,该类为抽象类,不能进行实例化操作
class Animal
{
public:
    Animal()
	{
		cout<<"Animal默认构造函数调用"<<endl;
	}
    
    // 纯虚析构 - 既要有声明,也须有定义;类内声明,类外定义。子类不会继承父类中的析构函数,因此在子类中无须重写父类的析构函数。
    virtual ~Animal() = 0;
 
    virtual void speak()
	{
		cout<<"动物在说话"<<endl;
	}
};
Animal::~Animal()
{
    cout<<"Animal纯虚析构函数调用"<<endl;
}
 
class Cat: public Animal
{
private:
    char* name;
public:	
	Cat(const char* name)
    {
		cout<<"Cat有参构造函数调用"<<endl;
		this->name = new char[strlen(name)+1];
		strcpy(this->name, name);
	}
	
	~Cat()
    {
		cout<<"Cat析构函数调用"<<endl;
		if(NULL!=this->name)
		{
			delete[] this->name;
			this->name = NULL;
		}
	}
    
	void speak()
	{
		cout<<this->name<<" 小猫在说话"<<endl;
	}
};
 
void doSpeak(Animal* animal)
{
	animal->speak();
	delete animal;
	animal = NULL;
}
 
int main()
{
	doSpeak(new Cat("小黑"));
	return 0;
}
相关推荐
微尘81 分钟前
C语言存储类型 auto,register,static,extern
服务器·c语言·开发语言·c++·后端
liangbm38 分钟前
MATLAB系列05:自定义函数
开发语言·笔记·matlab·教程·函数·自定义函数·按值传递
金博客21 分钟前
Qt 模型视图(二):模型类QAbstractItemModel
c++·qt6.7.2
五味香39 分钟前
C++学习,动态内存
java·c语言·开发语言·jvm·c++·学习·算法
无名之逆40 分钟前
计算机专业的就业方向
java·开发语言·c++·人工智能·git·考研·面试
Beauty.56844 分钟前
P1328 [NOIP2014 提高组] 生活大爆炸版石头剪刀布
数据结构·c++·算法
jimmy.hua1 小时前
C++刷怪笼(5)内存管理
开发语言·数据结构·c++
神之王楠1 小时前
学习风格的类型
学习
xiaobai12 31 小时前
二叉树的遍历【C++】
开发语言·c++·算法