- C++面向对象来源于生活,每个对象都有初始化设置和销毁前的清理数据的设置。
2.1 构造函数和析构函数
(1)构造函数
- **++初始化++**对象的成员属性
- 不提供构造函数时,编译器会提供不带参数的默认构造函数,函数实现是空的
- 构造函数不需要调用,编译器会自动调用
cpp
class Test
{
private:
int m_var;
public:
// 1.构造函数名称与类名相同
// 2.没有返回值,不需要写void
// 3.可以有参数,可以进行重载
Test(int var) // 构造函数
{
m_var = var; // 初始化成员属性
}
};
(2)析构函数
- ++释放对象++
- 不提供析造函数时,编译器会提供默认构造函数,函数实现是空的
- 析构函数不需要调用,编译器会自动调用
cpp
class Test
{
private:
int m_var;
public:
Test(int var) // 构造函数
{
m_var = var; // 初始化成员属性
}
// 1.析构函数名称与类名相同
// 2.没有返回值,不需要写void
// 3.不可以有参数,不可以进行重载
~Test()
{ /* 清理数据 */ }
};
2.2 构造函数分类与调用
(1)分类方法
-
按**++参数++**分类:有参数构造、无参数构造
cppclass Test { public: int m_var; public: Test() // 无参构造函数(默认构造) {} Test(int var) // 有参构造函数 { m_var = var; // 初始化成员属性 } };
-
按**++类型++** 分类:普通构造、拷贝构造
cppclass Test { public: int m_var; public: Test(const Test & test) // 拷贝构造函数(将传入的对象的属性拷贝到当前对象) { m_var= test.m_var; } };
(2)调用方式
-
括号法
cppint main() { int var = 10; Test test1; // 无参构造调用,不要加() Test test2(10); // 有参构造调用 Test test3(test2); // 拷贝构造调用 }
-
显示调用法
cppint main() { Test test1; // 无参构造调用,不要加() Test test2 = Test(10); // 有参构造调用 Test test3 = Test(test2); // 拷贝构造调用 Test(10); // 匿名对象,当前行执行完后,系统立即清除该匿名对象 }
-
隐式转换法
cppint main() { Test test1 = 10; // 有参构造调用 Test test2 = test1; // 拷贝构造调用 }
2.3 拷贝构造函数使用时机
- 使用已创建 的对象初始化一个新的对象
- 值传递的方式将对象作为函数的参数进行传递
cpp
void func(Test test)
{
std::cout << test.m_var << std::endl;
}
int main()
{
Test test1 = 10;
func(test1); // 值传递方式传对象
}
- 值返回的方式将一个局部对象在函数中进行返回
cpp
Test func()
{
Test test(10);
return test; // 返回局部对象
}
int main()
{
Test test1(10);
func(test1); // 值传递方式传对象
}
2.4 构造函数调用规则
(1)默认情况下,C++编译器会为一个类提供:
- 默认构造函数-->无参,函数体空
- 默认析构函数-->无参,函数体空
- 默认拷贝构造函数-->类属性的值拷贝
(2)构造函数调用规则:
- 如果自定义了有参构造函数,编译器将不再提供默认无参构造函数,但依然会提供默认拷贝构造函数
- 如果自定义了**++拷贝构造函数++** ,编译器将不再提供**++其他构造函数++**
2.5 深拷贝和浅拷贝
(1)浅拷贝
- 简单赋值拷贝操作,存在析构同一个堆指针两次的风险
cpp
class Test
{
public:
int m_var;
int *m_varp;
public:
Test(var, varp)
{
m_var = var;
m_varp = new int(varp); // 从堆申请新的内存块
}
~Test()
{
if(m_varp != NULL)
{
delete m_varp; // 堆上开辟的内存需要主动消除
m_varp = NULL; // 避免出现野指针
}
}
};
int main()
{
Test test(10, 20);
Test test2(test); // 调用默认拷贝构造函数(浅拷贝),仅拷贝指针变量,但两个指针指向
// 同一块内存,调用析构函数时,将会delete同一块堆内存两次,报错
}
(2)深拷贝
- 在堆区重新申请空间,进行拷贝操作
cpp
class Test
{
public:
int m_var;
int *m_varp;
public:
Test(var, varp)
{
m_var = var;
m_varp = new int(varp); // 从堆申请新的内存块
}
Test(const Test & test) // 自行定义深拷贝构造函数
{
m_var = test.m_var;
m_varp = new int(*(test.m_varp); // 重新开辟自己的堆内存块
}
~Test()
{
if(m_varp != NULL)
{
delete m_varp; // 堆上开辟的内存需要主动消除
m_varp = NULL; // 避免出现野指针
}
}
};
2.6 初始化列表
- 类在构造函数中使用初始化列表**++给属性进行赋初值++**
cpp
class Test
{
public:
int m_var;
int m_var1;
public:
Test(int var, int var1):m_var(var),m_var1(var1)
{ /* 初始化列表给属性赋初值,构造函数体中不在做赋初值操作 */ }
};
2.7 类对象作为类成员
- 当其他类对象作为本类成员时,++先构造其他类对象++,再构造本类对象
- 析构函数与构造函数顺序相反
cpp
class Phone
{
public:
std::string m_pName;
Phone(std::string pName)
{
m_pName = pName
}
}
class Person
{
public:
std::string m_name;
Phone m_phone;
// m_phone(pname)等价于Phone phone = pname
// 即Phone有参构造函数的隐式调用
Person(std::string name, std::string pname):m_name(name),m_phone(pname)
{}
}
2.8 静态成员
- 在成员变量或成员函数前,加上关键字"static",就变成了静态成员
(1)静态成员变量
- 所有对象共享一份数据 --> 所有对象都可以修改
- 在**++编译阶段++**分配内存
- 类内声明,++类外初始化++
cpp
class Test
{
public:
static int m_var; // 静态成员变量(类内声明)
};
int Test::m_var = 100; // 静态成员变量(类外初始化)
int main()
{
// 静态成员变量的两种访问方式:
// 1.对象访问 --> 对象.变量
Test test;
std::cout << test.m_var << std::endl;
// 2.类名访问 --> 类名::变量
std::cout << Test::m_var << std::endl;
}
(2)静态成员函数
- 所有对象共享静态成员函数
- 静态成员函数只能访问静态成员变量,不能访问非静态成员变量(++无法区分非静态成员变量属于哪个对象++)
cpp
class Test
{
public:
static int m_var;
static void func() // 静态成员函数
{ m_var = 200; } // 只能访问静态成员变量
};
int Test::m_var = 100;
int main()
{
// 静态成员函数的两种访问方式:
// 1.对象访问 --> 对象.函数
Test test;
test.func();
// 2.类名访问 --> 类名::函数
Test::func();
}