对象的构造和析构
新建一个对象时,要初始化,使用完之后要及时清理
由构造函数和析构函数来完成
1.构造函数和析构函数的语法:
构造函数语法:
构造函数函数名和类名相同,没有返回值,不能有void,但可以有参数。
ClassName(){}
析构函数语法:
析构函数函数名是在类名前面加”~”组成,没有返回值,不能有void,不能有参数,不能重载。
~ClassName(){}
2.构造函数的分类和调用:
分类:
无参构造函数、
Person(){
cout << "no param constructor!" << endl;
mAge = 0;
}
有参构造函数、
Person(int age){
cout **<<** "1 param constructor!" **<<** endl**;**
mAge **=** age**;**
**}**
拷贝构造函数
Person**(**const Person**&** person**){**
cout **<<** "copy constructor!" **<<** endl**;**
mAge **=** person**.**mAge**;**
**}**
调用:
调用无参构造函数:Person person1**;**
调用有参构造函数:
1)括号法:
Person person01**(**100**);**
2)匿名对象:
Person**(**200**);** //匿名对象,没有名字的对象
Person person03 **=** Person**(**300**);**
3)=号法:
Person person04 **=** 100**;** //Person person04 = Person(100)
初始化列表:
传统初始化:
Person(int a,int b,int c){mA = a;mB = b;mC = c;}
初始化列表:
Person(int a, int b, int c):mA(a),mB(b),mC(c){}
构造函数调用规则:n 默认情况下,c++编译器至少为我们写的类增加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对类中非静态成员属性简单值拷贝
如果用户定义拷贝构造函数,c++不会再提供任何默认构造函数
如果用户定义了普通构造(非拷贝),c++不在提供默认无参构造,但是会提供默认拷贝构造
3、深拷贝,浅拷贝
浅拷贝
一般情况下,浅拷贝没有任何副作用,但是当类中有指针,并且指针指向动态分配的内存空间,析构函数做了动态内存释放的处理,会导致内存问题。
深拷贝
当类中有指针,并且此指针有动态分配空间,析构函数做了释放处理,往往需要自定义拷贝构造函数,自行给指针动态分配空间,深拷贝。
class Person{
public:
Person(char* name,int age){
pName = (char*)malloc(strlen(name) + 1);
strcpy(pName,name);
mAge = age;
}
//增加拷贝构造函数
Person(const Person& person){
pName = (char*)malloc(strlen(person.pName) + 1);
strcpy(pName, person.pName);
mAge = person.mAge;
}
~Person(){
if (pName != NULL){
free(pName);
}
}
private:
char* pName;
int mAge;
};
void test(){
Person p1("Edward",30);
//用对象p1初始化对象p2,调用c++提供的默认拷贝构造函数
Person p2 = p1;
}
4、多个对象构造和析构
当调用构造函数时,首先按各对象成员在类定义中的顺序(和参数列表的顺序无关)依次调用它们的构造函数,对这些对象初始化,最后再调用本身的函数体。也就是说,先调用对象成员的构造函数,再调用本身的构造函数。析构函数和构造函数调用顺序相反,先构造,后析构。
例如:
类A汽车
类B拖拉机
类C人,内含对象成员 汽车对象,拖拉机对象
//初始化列表可以指定调用构造函数
Person(string carName, string tracName, string name) : mTractor(tracName), mCar(carName), mName(name){
5.exlicit关键字:
c++提供了关键字explicit,禁止通过构造函数进行的隐式转换。声明为explicit的构造函数不能在隐式转换中使用
6.动态对象创建(new和delete):
C中malloc和free在运行时从堆中分配存储单元
当创建一个c++对象时会发生两件事:
-
为对象分配内存
-
调用构造函数来初始化那块内存
C++中使用运算符New和delete来动态分配内存:
当用new创建一个对象时,它就在堆里为对象分配内存并调用构造函数完成初始化。它带有内置的长度计算、类型转换和安全检查。这样在堆创建一个对象和在栈里创建对象一样简单。
Person***** person = new Person;
new表达式的反面是delete表达式。delete表达式先调用析构函数,然后释放内存。正如new表达式返回一个指向对象的指针一样,delete需要一个对象的地址。delete只适用于由new创建的对象
使用new和delete在堆上创建数组:
//创建字符数组
char* pStr = new char[100];
//创建整型数组
int* pArr1 = new int[100];
//创建整型数组并初始化
int* pArr2 = new int[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//释放数组内存
delete[] pStr;
delete[] pArr1;
delete[] pArr2;
如果对一个void*指针执行delete操作,这将可能成为一个程序错误,除非指针指向的内容是非常简单的,因为它将不执行析构函数.
7.静态成员变量
用关键字static修饰,为静态,称为静态成员
不管类创建了多少个对象,静态成员只有一个拷贝,拷贝被所有属于这个类的对象共享
- 静态成员变量
静态变量,是在编译阶段就分配空间,对象还没有创建时,就已经分配空间。
1)静态成员变量必须在类中声明,类外定义
2)静态数据成员不属于某个对象,在为对象分配空间中不包括静态成员所占空间
3)静态数据成员可以通过类名或者对象名来引用
class Person{
public:
//类的静态成员属性
static int sNum;
private:
static int sOther;
};
//类外初始化,初始化时不加static
int Person::sNum = 0;
int Person::sOther = 0;
int main(){
//1. 通过类名直接访问
Person::sNum = 100;
cout << "Person::sNum:" << Person::sNum << endl;
//2. 通过对象访问
Person p1, p2;
p1.sNum = 200;
cout << "p1.sNum:" << p1.sNum << endl;
cout << "p2.sNum:" << p2.sNum << endl;
//3. 静态成员也有访问权限,类外不能访问私有成员
//cout << "Person::sOther:" << Person::sOther << endl;
Person p3;
//cout << "p3.sOther:" << p3.sOther << endl;
system("pause");
return EXIT_SUCCESS;
}
2.静态成员函数
在类定义中,前面有static说明的成员函数称为静态成员函数。静态成员函数使用方式和静态变量一样,同样在对象没有创建前,即可通过类名调用。静态成员函数主要为了访问静态变量,但是,不能访问普通成员变量。静态成员函数的意义,不在于信息共享,数据沟通,而在于管理静态数据成员,完成对静态数据成员的封装。
3.const静态成员属性
const static int mShare = 10;
只读,不可改变,共享
4.单例模式
通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其默认构造函数和拷贝构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。