文章目录
1 构造函数和析构函数
1.1 对象初始化和清理的背景
背景:对象的初始化和清理涉及程序安全。
①对象或变量使用前若未初始化,则程序运行结果存在不确定性,即不可预测;
②对象或变量使用完毕后若未及时清理,则会导致内存泄露。
1.2 构造函数和析构函数的特点
C++中,创建对象时会调用构造函数进行初始化设置;销毁对象时会调用析构函数进行数据清理设置。
C++对象初始化(构造函数)和清理(析构函数)的特点:
(1)编译器会强制执行对象的初始化和清理工作;
(2)编译器会自动调用构造函数和析构函数,无须手动调用;
(3)若程序员未自定义构造函数和析构函数,则编译器会提供空实现的构造函数和析构函数,即函数体代码为空。
构造函数和析构函数的作用:
构造函数
:创建对象时为对象的成员属性赋值,由编译器自动调用,无须手动调用。
析构函数
:对象销毁前由编译器自动调用,执行数据清理工作。
1.3 构造函数(可重载)
作用:创建对象时为对象的成员属性赋值,由编译器自动调用,无须手动调用。
语法:类名(){...}
特点:
(1)构造函数无返回值,且不写void
;
(2)函数名与类名相同;
(3)构造函数可包含参数,即可函数重载;
(4)创建对象时编译器会自动调用构造函数,无须手动调用,且仅调用一次。
注1:若在类外通过调用构造函数创建对象,其访问权限需为公共权限
public
。
注2:C++和Java创建对象的区别:
C++创建对象时,会自动调用构造函数,无需显式调用构造函数,如Person p;
即会开辟堆区空间;
Java创建对象时,必须使用new操作符
显式手动调用构造方法,才会开辟堆区空间,如Person p = new Person();
;否则Person p;
仅创建空引用(空的对象引用),未指向/绑定任何对象,不会开辟堆区空间,运行时编译器提示:尚未初始化变量。
1.4 析构函数(不可重载)
作用:对象销毁前由编译器自动调用,执行数据清理工作(如释放堆区数据)。
语法: ~类名(){...}
特点:
(1)析构函数无返回值,且不写void
;
(2)函数名与类名相同,且在函数名前使用波浪号~
;
(3)析构函数不可包含参数,即不可函数重载;
(4)对象销毁前编译器会自动调用析构函数,无须手动调用,且仅调用一次。
注1:若类的成员变量需在堆区开辟内存空间,则需要:
①自定义析构函数以释放成员变量申请的堆区内存;
②使用深拷贝自定义拷贝构造函数,防止默认拷贝构造函数的浅拷贝导致重复释放内存。
注2:在堆区创建的对象,对对象指针使用delete操作符
后,调用自定义的析构函数。
示例1:在main函数中创建对象
#include <iostream>
using namespace std;
class Object
{
public:
//自定义构造函数
Object()
{
cout << "构造函数被自动调用" << endl;
}
//自定义析构函数
~Object()
{
cout << "析构函数被自动调用" << endl;
}
};
int main() {
//创建对象时:“构造函数被自动调用”
Object obj;
system("pause"); //任意键继续【main函数未执行完毕】
//销毁对象前(按下任意键后):“析构函数被自动调用”
return 0;
}
示例2:在全局函数中创建对象,在main函数中调用该全局函数
#include <iostream>
using namespace std;
class Object
{
public:
//自定义构造函数
Object()
{
cout << "构造函数被自动调用" << endl;
}
//自定义析构函数
~Object()
{
cout << "析构函数被自动调用" << endl;
}
};
/* 全局函数中创建对象 */
void func(){
Object obj;
}
int main() {
/* 函数执行时,创建对象;函数结束后,销毁对象 */
func(); //“构造函数被自动调用” “析构函数被自动调用”
system("pause"); //任意键继续【main函数未执行完毕】
return 0;
}
2 构造函数的分类及调用方式
分类方式:
(1)按参数划分:无参构造(默认构造)
、有参构造
(2)按类型划分:普通构造
、拷贝构造
拷贝构造函数:使用同类对象初始化新创建的对象。
语法:类名(const 类名 &对象引用名){...}
,例如:Object(const Object &obj){...}
特点:
(1)构造函数的形参传入实参同类对象,拷贝其属性对新对象进行初始化赋值;
(2)构造函数的形参中被传入的对象不能被修改,使用const关键字
限定为只读权限;
(3)构造函数的形参使用引用(别名)接收对象变量,可避免数据拷贝,减少内存占用。
调用方式:
(1)小括号调用带参构造函数或拷贝构造函数。
语法:类名 对象名(参数值..);
,如Object obj(param...);
。
注:调用默认无参构造函数时,不能使用小括号,如
Object obj;
;
若使用小括号且无参,则编译器会判定为函数声明,如Object obj();
,即返回类型为Object类型的函数obj()
(函数体内部可包含函数声明)。
Object o; //调用无参构造函数
Object obj(1); //调用带参构造函数
Object obj2(obj); //调用拷贝构造函数使用已有对象obj创建对象obj2
(2)显式初始化调用带参构造函数或拷贝构造函数。
语法:类名 对象名 = 类名(参数值..);
,如Object obj = Object(param);
。
注1:Java中显式初始化需使用
关键字new
,如Object obj = new Object(args...);
;
C++中显式初始化不使用关键字new
。
注2:表达式Object obj = Object(args);
中,赋值符的右侧部分Object(args)
可表示匿名对象。当匿名对象所在语句执行完毕后,系统会立即回收匿名对象。
注3:不能使用拷贝构造函数初始化匿名对象,编译器会认为是对象的声明,否则编译器报错:(当前对象)重定义
。编译器内部若使用已有对象obj,通过拷贝构造函数创建匿名对象Object(obj);
,等价于声明对象Object obj;
导致对象obj被重复定义。
Object obj = Object(1); //调用带参构造函数
Object obj2 = Object(obj); //通过拷贝构造函数使用已有对象obj创建非匿名对象obj2
/* 不能使用拷贝构造函数初始化匿名对象 */
//编译器报错:"Object obj2"重定义
//Object(obj); //错误:通过拷贝构造函数使用已有对象obj创建匿名对象,等价于Object obj;
(3)隐式转换法:对于单参数构造函数,赋值符右侧直接使用传入数据,相当于调用带参构造函数或拷贝构造函数。
语法:类名 对象名 = 参数值 或 对象名;
,如Object obj = 1;
或Object obj2 = obj;
。
Object obj = 1; //Object obj = Object(1); //相当于调用带参构造函数
Object obj2 = obj; //Object obj2 = Object(obj); //相当于调用拷贝构造函数
示例:
#include <iostream>
using namespace std;
class Person{
public:
int _age;
//无参构造
Person() {
cout << "调用无参构造函数" << endl;
}
//带参构造
Person(int age) {
_age = age;
cout << "调用带参构造函数" << endl;
}
//拷贝构造函数
Person(const Person& p) {
_age = p._age;
cout << "调用拷贝构造函数" << endl;
}
//析构函数
~Person(){
cout << "调用析构函数" << endl;
}
};
int main() {
/* 调用方式1:小括号调用 带参构造函数 或 拷贝构造函数 */
Person p; //无参构造
Person p11(10); //带参构造
Person p12(p11); //拷贝构造
/* 调用方式2:显式初始化调用 带参构造函数 或 拷贝构造函数 */
Person p21 = Person(10); //带参构造
Person p22 = Person(p21); //拷贝构造
//不能使用拷贝构造函数创建匿名对象,报错:对象重定义
//Person(p22); //等价于对象声明 Person p22; 重复定义对象p22
/* 调用方式3:隐式转换法调用 带参构造函数 或 拷贝构造函数 */
Person p31 = 10; //带参构造
Person p32 = p31; //拷贝构造
return 0;
}
3 拷贝构造函数的调用时机
C++中使用拷贝构造函数的3类场景:
(1)使用已创建完毕的对象初始化一个新对象,会调用拷贝构造函数;【最常用】
Object obj; //使用无参构造创建对象obj
Object object(obj); //调用拷贝构造函数使用对象o创建新对象object
(2)通过值传递的方式向函数形参传递对象,会调用拷贝构造函数,创建实参对象的拷贝并使用形参接收;
void func(Object object){...}
Object obj; //使用无参构造创建对象obj
func(obj); //值传递:向函数func()传递实参obj对象时,调用拷贝构造函数
注:实参obj对象和接收实参的形参object对象是两个不同的对象,object对象的修改不会影响obj对象。
(3)函数中以值方式返回函数内部创建的局部对象,会调用拷贝构造函数,创建局部对象的拷贝(局部对象会被释放)。
Object func(){
Object obj; //在函数内部创建局部对象
return obj; //返回局部对象时会调用拷贝构造函数创建一份局部对象的拷贝;局部对象被释放
}
Object object = func(); //接收函数的返回值(对象类型)
示例:
#include <iostream>
using namespace std;
class Person {
public:
int _age;
//无参构造
Person() {
cout << "调用无参构造函数" << endl;
}
//带参构造
Person(int age) {
_age = age;
cout << "调用带参构造函数" << endl;
}
//拷贝构造函数
Person(const Person& p) {
_age = p._age;
cout << "调用拷贝构造函数" << endl;
}
//析构函数
~Person() {
cout << "调用析构函数" << endl;
}
};
/* 场景2:通过值传递的方式向函数形参传递实参对象,会调用拷贝构造函数 */
void func1(Person person) {
person._age = 50;
cout << "接收实参中对象变量的形参对象地址:" << &person << endl;
}
/* 场景3:函数中以值方式返回函数内部创建的局部对象,会调用拷贝构造函数 */
Person func2() {
Person temp;
cout << "函数中局部对象的地址:" << &temp << endl;
return temp;
}
int main() {
/* 场景1:使用已创建的对象去初始化新对象 */
//Person p11;
//Person p12(p11);
/* 场景2:通过值传递的方式向函数形参传递实参对象,会调用拷贝构造函数 */
//Person p2;
//cout << "实参对象p2的地址:" << &p2 << endl;
//func1(p2);
/* 场景2输出结果:
调用无参构造函数
实参对象p21的地址:00B9F7F8
调用拷贝构造函数
接收实参中对象变量的形参对象地址:00B9F714
调用析构函数
调用析构函数
*/
/* 场景3:函数中以值方式返回函数内部创建的局部对象,会调用拷贝构造函数 */
Person p3 = func2();
cout << "函数返回局部对象p3的地址:" << &p3 << endl;
/* 场景3输出结果:
调用无参构造函数
函数中局部对象的地址:0057F6F8
调用拷贝构造函数
调用析构函数
函数返回局部对象p3的地址:0057F7FC
调用析构函数
*/
return 0;
}
4 构造函数的调用规则
创建一个类时,C++编译器默认会对类添加至少3个函数:
(1)默认构造函数(无参,函数体为空,即空实现)
(2)默认析构函数(无参,函数体为空,即空实现)
(3)默认拷贝构造函数(对属性进行值拷贝)
构造函数的调用规则:
(1)若用户自定义带参构造函数,C++编译器不再提供默认无参构造函数(需用户显式提供无参构造),但会提供默认拷贝构造函数(属性值拷贝)。
注1:若用户自定义带参构造函数,且未定义无参构造函数时,由于编译器不再提供默认无参构造函数,调用无参构造函数时,编译器报错:
(XX类)没有合适的默认构造函数可用
或类“XX”不存在默认构造函数
。
注2:当用户自定义带参构造函数时,编译器不再提供默认无参构造函数,可使用类名() = default;
自动生成空函数体{}
,即恢复默认无参构造函数。【默认析构函数类似】
例:Object() = default;
等价于默认无参构造函数Object(){}
。
#include <iostream>
using namespace std;
class Person {
public:
int _age;
//自定义带参构造
Person(int age) {
_age = age;
cout << "调用自定义带参构造函数" << endl;
}
//析构函数
~Person() {
cout << "调用析构函数" << endl;
}
};
int main() {
/* 若用户自定义带参构造函数,则编译器不再提供默认无参构造 */
//无法访问默认无参构造函数
//Person p; //报错:类“Person”不存在默认构造函数;“Person”没有合适的默认构造函数可用
/* 若用户自定义带参构造函数,则编译器仍会提供默认默认拷贝构造函数(值拷贝) */
//可以访问默认拷贝构造函数
Person p1(18);
Person p2(p1);
cout << p2._age << endl; //18
return 0;
}
(2)若用户自定义拷贝构造函数,C++编译器不再提供其它普通构造函数(默认无参构造和带参构造均不提供)。
注:若用户自定义拷贝构造函数,且未定义其它普通构造函数(含无参构造和带参构造)时,由于编译器不再提供默认其它普通构造函数,调用其它普通构造函数时,编译器报错:
(XX类)没有与参数列表匹配的构造函数
。
#include <iostream>
using namespace std;
class Person {
public:
int _age;
//自定义拷贝构造函数
Person(const Person& person) {
cout << "自定义拷贝构造函数" << endl;
_age = person._age;
}
//析构函数
~Person() {
cout << "调用析构函数" << endl;
}
};
int main() {
/* 若用户自定义拷贝构造函数,则编译器不再提供其它普通构造函数 */
//无法访问默认无参构造函数
//Person p1; //报错:类“Person”不存在默认构造函数;“Person”没有合适的默认构造函数可用
//无法访问未定义的带参构造函数
//Person p2(18); //报错:没有与参数列表匹配的构造函数;无法将参数1从 int 转换为 const Person &
return 0;
}
5 浅拷贝与深拷贝
浅拷贝:简单的赋值拷贝(属性值拷贝)。
深拷贝:在堆区申请新的内存空间,前后两块堆区内存的地址不同。
默认拷贝构造函数 VS 自定义拷贝构造函数:
默认拷贝构造函数
:采用浅拷贝,简单拷贝属性值。
使用浅拷贝的默认拷贝构造函数,会导致指针类型的成员变量指向的(堆区)内存地址相同,对象释放时堆区内存被重复释放(再次释放已释放的内存,为非法操作),导致程序运行崩溃。
自定义拷贝构造函数
:采用深拷贝,通过解引用操作
和new操作符
,使指针类型的成员变量指向的内存地址不同、对应值相同,即调用拷贝构造函数时,会重新申请一块堆区内存空间,创建的对象不再指向相同的堆区内存地址。
例如:pField = new int(*obj.pField);
解引用获取指针变量对应的值;new操作符重新申请堆区内存空间。
注:若类的成员变量需在堆区开辟内存空间,则需要:
①自定义析构函数以释放成员变量申请的堆区内存;
②使用深拷贝自定义拷贝构造函数,防止默认拷贝构造函数的浅拷贝导致重复释放内存。
示例:
#include <iostream>
using namespace std;
class Student {
public:
int _age;
int *pScore; //指针类型的成员变量
//自定义无参构造
Student() {
cout << "调用自定义无参构造函数" << endl;
}
//自定义带参构造
Student(int age, int score) {
_age = age;
//new操作符返回堆区内存的地址,使用指针类型成员变量接收
pScore = new int(score);
cout << "调用自定义带参构造函数" << endl;
}
//自定义拷贝构造函数
Student(const Student& stu) {
_age = stu._age;
/* 默认拷贝构造的浅拷贝-属性值拷贝,会导致指向的堆区内存为同一个,析构时重复释放出错 */
//默认拷贝构造函数-浅拷贝:简单属性值拷贝
//pScore = stu.pScore; //运行出错
//自定义拷贝构造函数-深拷贝:使用new操作符申请堆区内存空间
pScore = new int(*stu.pScore); //对指针变量解引用获取对应的值
cout << "调用自定义拷贝构造函数" << endl;
}
//自定义析构函数:释放堆区内存
~Student() {
if (pScore != NULL) {
delete pScore; //delete操作符释放堆区内存
pScore = NULL; //指针置为NULL,防止出现野指针
}
cout << "调用自定义析构函数" << endl;
}
};
int main() {
/*
默认拷贝构造函数采用浅拷贝,指针类型成员变量被赋值相同的地址值,指向同一块内存地址;
程序运行结束后,对象被释放,导致同一块内存被多次释放,导致运行出错。
需自定义拷贝构造函数,并采用深拷贝,重新申请一块堆区内存空间,创建的对象不再指向同一块内存地址。
*/
//使用自定义带参构造函数创建对象
Student stu1(18, 99);
//使用默认 / 自定义拷贝构造函数创建对象
Student stu2(stu1);
return 0;
}
6 构造函数的初始化列表
作用:构造函数的初始化列表用于初始化属性。
用法:初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。
语法:
(1)构造函数():成员属性1(初始值1),成员属性2(初始值2)... {}
例:Object():fieldA(10), fieldB(20), fieldC(30)...{}
(2)构造函数(形参列表):成员属性1(形参1),成员属性2(形参2)... {}
例:Object(int x, int y, int z):fieldA(x), fieldB(y), fieldC(z)...{}
注:构造函数的初始化列表可结合隐式转换法,创建对象成员。
示例:
#include <iostream>
using namespace std;
class Object {
private:
int A;
int B;
int C;
public:
/*
//构造函数显式初始化
Object(int a, int b, int c) {
A = a;
B = b;
C = c;
}
*/
//初试化列表-固定初始值
Object() :A(1), B(2), C(3) {}
//初始化列表-显式赋值
Object(int x, int y, int z) :A(x), B(y), C(z) {}
void print() {
cout << "A:" << A << endl;
cout << "B:" << B << endl;
cout << "C:" << C << endl;
}
};
int main() {
Object obj1;
obj1.print(); //1 2 3
Object obj2(6,7,8);
obj2.print(); //6 7 8
return 0;
}
7 对象成员:类对象作为类成员
类中成员是另一个类的对象,则该成员为对象成员。
注:构造函数的初始化列表可结合隐式转换法,创建对象成员。
例:类B中包含成员类A的对象,即对象成员。
class A {};
class B
{
A a;
};
类对象和对象成员的调用顺序:
当其它类的对象作为本类成员(对象成员)时,
(1)构造函数的调用顺序:先调用对象成员的构造函数创建对象成员,再调用本类构造函数创建本类对象;
(2)析构函数的调用顺序:先调用本类的析构函数释放本类对象,再调用对象成员的析构函数释放对象成员。
注:构造函数和析构函数的调用时机
①对象成员的构造函数→创建对象成员
②本类的构造函数→创建本类对象
③本类的析构函数→释放本类对象
④对象成员的析构函数→释放对象成员
示例:
#include <iostream>
using namespace std;
#include <string>
class Car {
public:
string brandName;
Car(string bName) {
brandName = bName;
cout << "Car类的构造函数" << endl;
}
~Car() {
cout << "Car类的析构函数" << endl;
}
};
class Person {
public:
string name;
Car car;
//初始化列表与隐式转换法 创建对象:Car car = bName; 等价于 Car car = Car(bname);
Person(string pName, string bName):name(pName),car(bName) {
cout << "Person类的构造函数" << endl;
}
~Person() {
cout << "Person类的析构函数" << endl;
}
};
int main() {
Person p("Tom", "Tesla");
cout << "姓名:" << p.name << ",座驾:" << p.car.brandName << endl;
/* //输出结果
Car类的构造函数
Person类的构造函数
姓名:Tom,座驾:Tesla
Person类的析构函数
Car类的析构函数
*/
return 0;
}
8 explicit关键字
作用:修饰单参数构造函数,使用explicit关键字
可禁止通过隐式转换创建对象。
注:使用
explicit关键字
修饰单参数构造函数后,隐式转换时编译器报错:不存在从(单参数数据类型)转换到(类类型)的适当构造函数
。
示例:
#include <iostream>
using namespace std;
class Object1 {
private:
int var;
public:
//单参数构造函数,不使用explicit关键字
Object1(int value) {
var = value;
}
};
class Object2 {
private:
int var;
public:
//单参数构造函数,使用explicit关键字
explicit Object2(int value) {
var = value;
}
};
int main() {
//隐式转换,等价于Object1 obj1 = Object1(10);
Object1 obj1 = 10; //正确
//对单参数构造函数使用explicit关键字后,不能通过隐式转换法创建对象
//Object2 obj2 = 20; //错误:不存在从int转换到Object2的适当构造函数
return 0;
}
9 动态对象创建(new、delete操作符)
C++创建对象时,需完成的动作:
(1)为对象分配内存;
(2)调用构造函数初始化内存。
注:若使用未初始化的对象,可能导致程序出错。
C++中可使用new/delete操作符
或malloc/free标准库函数
创建对象。
C语言动态内存分配的方法:
兼容C语言的malloc/calloc/realloc库函数和free库函数申请和释放内存时,存在如下问题:
(1)申请内存时,必须先确定对象的长度,如malloc(sizeof(Object));
(2)malloc返回void*
类型的指针,C++中不允许将void*
类型赋值给其它类型指针,必须强制转换。
例:Object *obj = (Object*)malloc(sizeof(Object));
//确定对象长度,并强转void*类型
(3)malloc申请内存时可能失败,必须判空处理以确保内存分配成功。
Object *obj = (Object*)malloc(sizeof(Object));
if(obj == NULL){
return 0;
}
(4)必须显式地初始化和释放内存。
注:C语言中不存在构造函数或析构函数,需自行封装函数并手动显式调用,用于初始化和释放内存。
9.1 new操作符
使用new
创建对象时,会在堆区为对象分配内存并调用构造函数完成初始化,返回堆区内存地址(可使用指向对应类型对象的指针接收)。
注:使用
new操作符
创建对象时:
(1)返回指向该类对象的指针,而不是void *
类型,无需强制转换;
(2)可确保堆区内存分配成功;
(3)可自动调用构造函数完成初始化,无需手动显式调用自行封装的初始化函数。
例:
使用new操作符创建对象
Object *obj = new Object; //返回Object*类型的指针,无需强转
使用malloc创建对象
Object *obj = (Object*)malloc(sizeof(Object)); //malloc分配内存,强转void*指针
if(obj == NULL){ //判断内存是否分配成功
return 0;
}
obj->Init(); //自行封装的初始化函数
开辟堆区变量:数据类型 *指针名 = new 数据类型(初始值);
开辟堆区数组:数据类型 *指针名 = new 数据类型[数组长度];
注1:若在堆区开辟自定义类型的数组,则该类必须包含默认构造函数。否则,编译器报错:
没有合适的默认构造函数可用
。
注2:若在栈区开辟自定义类型的数组,可以不包含默认构造函数。
/* 开辟堆区数组 */
int *pInt = new int[10]; //堆区整型数组
char *pChar = new char[64]; //堆区字符型数组
/* 在堆区开辟自定义类型的数组,则该类必须包含【无参构造函数】 */
//在堆区创建自定义类型数组时,会多次(数组长度)调用构造函数进行初始化
Object *objs = new Object[10]; //堆区自定义类型数组
9.2 delete操作符
使用delete
释放对象时,会先调用析构函数,再释放内存。
注1:
molloc函数
和free函数
、new操作符
和delete操作符
必须配对使用。
注2:delete只用用于释放new创建的对象,而不能释放由malloc/calloc/realloc
创建的对象,否则该行为是未定义的。new操作符
和delete操作符
的内部使用了molloc函数
和free函数
,若错误使用,可能会导致在调用析构函数前即释放内存。
注3:若删除的对象的指针为NULL
,则无任何影响。建议在删除指针后立即将指针赋值并置为NULL,避免多次删除对象指针时产生问题(多次删除NULL指针则无影响)。
释放堆区变量:delete 指针名;
释放堆区数组:delete[] 指针名;
示例:
#include <iostream>
using namespace std;
class Teacher{
public:
Teacher() {
cout << "默认无参构造函数" << endl;
}
Teacher(int age) {
_age = age;
cout << "带参构造函数" << endl;
}
Teacher(const Teacher &t) {
_age = t._age;
cout << "拷贝构造函数" << endl;
}
~Teacher() {
cout << "默认析构函数" << endl;
}
int _age;
};
//在堆区创建和释放对象
void func1() {
Teacher *p = new Teacher(15);
delete p;
}
//不要使用 void*类型接收 new操作符 创建的对象,否则使用delete操作符时不会执行析构函数。
void func2() {
void* p = new Teacher(15);
delete p; //不会调用析构函数
}
//在堆区开辟数组
void func3() {
int* pInt = new int[3];
char* pChar = new char[3];
/* 若在堆区开辟自定义类型的数组,则该类必须包含默认构造函数 */
Teacher* teachers = new Teacher[3]; //在堆区创建对象数组
delete[] teachers; //释放堆区对象数组
}
int main() {
//func1();
//func2();
func3();
return 0;
}
9.3 molloc/free和new/delete的区别
molloc/free和new/delete的区别:
(1)malloc/free属于库函数
;new/delete属于运算符
。
(2)molloc返回类型为void *
,需强制转换;new返回类型为相应数据类型的指针
,无需强制转换。
(3)molloc对应free;new对应delete。
(4)molloc 不会调用构造函数;new 会调用构造函数。
(5)free 不会调用析构函数;delete 会调用析构函数。
(6)malloc需要计算分配的内存大小;new不需要计算分配的内存大小。
注:不要使用
void*类型
接收new操作符
创建的对象,否则使用delete操作符
时不会执行析构函数。
void *p = new Object;
delete p; //若使用void*类型接收new创建的对象,delete不会执行析构函数
10 静态成员
静态成员:在成员变量或成员函数前使用关键字static
修饰。
10.1 静态成员变量
静态成员变量:被关键字static
修饰的成员变量。
(1)所有对象共享同一个静态成员变量,并非某个对象独有,即内存中仅存在一份数据;
(2)在编译阶段分配内存(程序运行前),位于全局区;
(3)在类内声明,在类外初始化,即静态变量必须具有初始值后才能使用。
注:类的大括号内和类的作用域内均属于类的内部,在类的大括号外使用
类名::成员
可访问类的私有化成员或私有化构造函数(private
修饰)。
class Object{
static int field; //类内声明
};
int Object::field = 10; //类外初始化
静态成员变量的访问方式:
(1)通过对象访问:对象.静态成员变量
(2)通过类名访问:类名::静态成员变量
注:静态成员变量存在访问权限:
private
修饰的静态成员变量在类外无法访问;
public
修饰的静态成员变量在类外可访问。
示例:静态成员变量
#include <iostream>
using namespace std;
class Object {
/* 静态成员变量存在访问权限:private修饰的在类外无法访问 */
public:
static int fieldA;
private:
static int fieldB;
};
/* 静态成员变量的初始化 */
int Object::fieldA = 1;
int Object::fieldB = 2;
int main() {
/* 静态成员变量:所有对象共享同一份数据 */
Object obj1;
Object obj2;
obj2.fieldA = 10;
//通过对象访问静态成员变量
cout << obj1.fieldA << endl; //10
cout << obj2.fieldA << endl; //10
//通过类名访问静态成员变量
cout << Object::fieldA << endl; //10
/* 静态成员变量存在访问权限:private修饰的在类外无法访问 */
//cout << Object::fieldB << endl; //不可访问
return 0;
}
10.2 静态成员函数
静态成员函数:被关键字static
修饰的成员函数。
(1)所有对象共享同一个静态成员函数;
(2)静态成员函数只能访问静态成员变量,不可访问非静态成员变量。
注:静态成员函数中不可访问非静态成员变量,无法区分非静态成员变量属于某个对象,编译器报错:
非静态成员引用必须与特定对象相对
。
静态成员函数的访问方式:
(1)通过对象访问:对象.静态成员函数()
(2)通过类名访问:类名::静态成员函数()
注:静态成员函数存在访问权限:
private
修饰的静态成员函数在类外无法访问;
public
修饰的静态成员函数在类外可访问。
示例:静态成员函数
#include <iostream>
using namespace std;
class Object {
public:
//静态成员函数
static void func() {
/* 静态成员函数可访问静态成员变量,不可访问非静态成员变量 */
s_field = 10;
//field = 20; //报错:非静态成员引用必须与特定对象相对
cout << "静态成员函数调用" << endl;
}
static int s_field; //静态成员变量
int field; //非静态成员变量
private:
/* 静态成员函数存在访问权限:private修饰的在类外无法访问 */
static void func2() {
cout << "静态成员函数调用" << endl;
}
};
int Object::s_field = 1;
int main() {
/* 静态成员函数:所有对象共享同一份数据 */
Object obj1;
Object obj2;
//通过对象访问静态成员变量
obj1.func();
obj2.func();
//通过类名访问静态成员变量
Object::func();
/* 静态成员函数存在访问权限:private修饰的在类外无法访问 */
//Object::func2(); //不可访问
return 0;
}
10.3 静态成员的应用:单例模式
单例模式:可保证一个类有且仅有一个实例,且该实例在外部易于访问。通过控制实例对象的个数,节省系统资源。
特点:
(1)对外提供静态成员函数getInstance()
的公共接口,方便外部获取唯一实例,且将实例限定为只读状态(避免外部将唯一实例置为NULL,导致无法再访问);
(2)为防止外部通过构造函数创建对象(实例化),将默认构造函数和拷贝构造函数私有化。
示例:单例模式案例-Boss类
#include <iostream>
using namespace std;
class Boss {
private:
/* 1.默认构造函数私有化 */
//防止外部通过构造函数创建对象
Boss() {}
/* 2.默认拷贝构造函数私有化 */
Boss(const Boss& b) {}
/* 3.静态成员变量:创建指向对象的指针,指向唯一的实例 */
static Boss* boss;
public:
/* 4.对外提供公共接口,获取唯一的实例,且限定为只读状态 */
//若未限定为只读状态,外部可能会将唯一实例置为 NULL
//静态成员函数只能访问静态成员变量,不可访问非静态成员变量
static Boss* getInstance() {
return boss;
}
};
/* 5.初始化唯一的实例:类的作用域内等价于类中,可访问私有构造函数 */
Boss* Boss::boss = new Boss;
int main() {
//通过类名访问静态方法
Boss* b1 = Boss::getInstance();
Boss* b2 = Boss::getInstance();
cout << ((b1 == b2) ? "b1 == b2" : "b1 != b2") << endl; //b1 == b2,即指向同一个对象
/*
//默认拷贝构造函数未私有化时,通过解引用对象指针获取对象并拷贝构造
Boss* b3 = new Boss(*b1); //*b1解引用获得对象,再调用拷贝构造函数,在堆区创建对象
cout << ((b1 == b3) ? "b1 == b3" : "b1 != b3") << endl; //b1 != b3,即指向不同的对象
*/
return 0;
}