构造函数和析构函数
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;
}