目录
构造函数和析构函数
- 构造函数:主要作用在于创造对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
- 析构函数:主要作用于在对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名称和类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时会自动调用构造,无需手动调用,而且只会调用一次
析构函数语法: ~类名 () {}
- 构造函数,没有返回值也不写void
- 函数名称和类名相同,在名称前加上符号~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无需手动调用,而且只会调用一次
例子:
注意:构造和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现的构造和析构 。
我们做一个实验,将person p放在main函数中实现,此时,我们可以看到析构函数是在system("pause")后面实现的。说明:这个对象在main函数走完才会执行释放,销毁操作,出现析构操作。
构造函数的分类及调用
两种分类的方式:
- 按参数分为:有参构造和无参构造(默认构造)
- 按类型分为:普通构造(不是拷贝构造的都叫普通构造)和拷贝构造
三种调用方法:
括号法![](https://i-blog.csdnimg.cn/blog_migrate/fa1791e014cc5eb04e86e0abc3b2a4e9.png)
-
注意:调用默认函数的构造,不加(),加上()编译器会认为是一个函数的声明。
显示法
- 注意:不要利用拷贝构造函数 初始化匿名对象
- 如:person(p3);
隐式转换法
拷贝构造函数调用时机
有三种:
1.使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
person p1(20);
person p2(p1);
}
2.值传递的方式给函数参数传值
void dowork(person p)
{
}
void test02()
{
person p;
dowork(p);
}
3.以值方式返回局部对象
person dowork2()
{
person p1;
return p1;
}
void test03()
{
person p=dowork2();
}
构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
深拷贝与浅拷贝
- 浅拷贝:简单的赋值拷贝操作
- 深拷贝:在堆区重新申请空间,进行拷贝操作
-
浅拷贝(Shallow Copy):
浅拷贝是一种简单的复制操作,它只复制对象的值(包括指针),而不会创建新的内存空间。浅拷贝会将原对象和新对象指向同一块内存地址。这意味着,如果一个对象的值发生变化,另一个对象也会受到影响。 -
深拷贝(Deep Copy):
深拷贝是一种复制操作,它会创建一个全新的独立对象,并复制对象的值和指针所指向的数据。深拷贝会为新对象分配新的内存空间,使得原对象和新对象完全独立。这意味着,如果一个对象的值发生变化,另一个对象不会受到影响。
#include <iostream>
class MyClass
{
public:
int* data;
// 构造函数
MyClass(int value)
{
data = new int;
*data = value;
}
// 拷贝构造函数(浅拷贝)
MyClass(const MyClass& other)
{
data = other.data;
}
// 赋值运算符重载(浅拷贝)
MyClass& operator=(const MyClass& other)
{
if (this != &other)
{
data = other.data;
}
return *this;
}
// 析构函数
~MyClass()
{
delete data;
}
};
int main()
{
// 创建对象1
MyClass obj1(10);
// 浅拷贝示例
MyClass obj2_shallow = obj1;
std::cout << "obj1.data: " << *obj1.data << std::endl;
std::cout << "obj2_shallow.data: " << *obj2_shallow.data << std::endl;
*obj1.data = 20; // 修改obj1的值
std::cout << "After modifying obj1.data..." << std::endl;
std::cout << "obj1.data: " << *obj1.data << std::endl;
std::cout << "obj2_shallow.data: " << *obj2_shallow.data << std::endl;
// 输出结果显示obj2_shallow的值也被修改了,因为浅拷贝只是复制了指针而没有复制数据
// 深拷贝示例
MyClass obj3_deep(obj1);
std::cout << "obj1.data: " << *obj1.data << std::endl;
std::cout << "obj3_deep.data: " << *obj3_deep.data << std::endl;
*obj1.data = 30; // 修改obj1的值
std::cout << "After modifying obj1.data..." << std::endl;
std::cout << "obj1.data: " << *obj1.data << std::endl;
std::cout << "obj3_deep.data: " << *obj3_deep.data << std::endl;
// 输出结果显示obj3_deep的值没有被修改,因为深拷贝创建了新的内存空间来存储数据
return 0;
}
初始化列表
效率更高:使用初始化列表可以直接在对象构造时初始化成员变量,而不是在构造函数体内逐个赋值。这样可以避免创建临时对象,并减少了额外的赋值操作,提高了代码的执行效率。
const成员变量的初始化:当类中有const修饰的成员变量时,只能使用初始化列表来进行初始化,因为const成员变量一旦被创建就不能再次赋值。
引用成员变量的初始化:引用成员变量必须在定义时进行初始化,并且只能通过初始化列表进行初始化。
基类和成员对象的初始化顺序:使用初始化列表可以指定基类和成员对象的初始化顺序,确保正确的初始化顺序。这对于继承或包含其他类的情况非常重要。
数组成员变量的初始化:如果类中包含数组成员变量,那么只能使用初始化列表来进行初始化。
更灵活的初始化选项:使用初始化列表,可以为成员变量提供不同的初始化选项,例如调用其他构造函数或者传递参数。
基本格式:
类对象作为类成员
例如:
class A{};
class B
{
A a;
}
示例中,我
们定义了一个Phone类来表示手机,它具有一个字符串类型的品牌成员和一个display()
函数来显示手机的品牌。
然后,我们定义了一个Student类,它具有一个名为phone的成员,类型为Phone,表示学生的手机。在Student的构造函数中,我们使用初始化列表phone(phoneBrand)
为phone成员变量初始化一个Phone对象。
在display()
函数中,我们首先显示学生的姓名,然后调用phone对象的display()
函数显示手机的品牌。
#include <iostream>
#include <string>
using namespace std;
class Phone
{
public:
Phone(const string& brand) : brand(brand) {}
void display()
{
cout << "Brand: " << brand << endl;
}
private:
string brand;
};
class Student
{
public:
Student(const string& name, const string& phoneBrand)
: name(name), phone(phoneBrand) {} // 初始化列表中使用Phone的构造函数
void display()
{
cout << "Name: " << name << endl;
cout << "Phone: ";
phone.display();
}
private:
string name;
Phone phone; // 使用Phone对象作为类成员
};
int main()
{
Student student("John", "Apple");
student.display();
return 0;
}
注意:当其他类对象作为本类成员,构造时候先构造类对象,在构造自身,析构的顺序与构造相反。
静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员。
静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
格式
class ClassName
{
public:
static dataType variableName; // 静态成员变量的声明
};
dataType ClassName::variableName = initialValue; // 静态成员变量的定义和初始化
两种访问方式
静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
格式:
class ClassName
{
public:
static returnType functionName(parameters); // 静态成员函数的声明
};
returnType ClassName::functionName(parameters)
{
// 函数实现
// 可以直接访问静态成员变量和调用其他静态成员函数
}
注意:
静态成员函数只能访问静态成员变量的原因是因为静态成员函数不依赖于类的对象,它们可以在没有创建类的对象的情况下被调用。由于没有对象,静态成员函数无法访问属于对象的非静态成员变量,因为这些变量是与具体的对象实例绑定在一起的。静态成员变量属于整个类,不依赖于任何对象,因此静态成员函数可以直接访问它们。
如:
#include <iostream>
using namespace std;
class MyClass
{
public:
static int staticVar; // 静态成员变量
static void staticFunction()
{
staticVar = 100; // 可以访问静态成员变量并修改其值
cout << "Static Variable: " << staticVar << endl;
}
};
int MyClass::staticVar = 0; // 静态成员变量的定义和初始化
int main()
{
MyClass::staticFunction(); // 通过类名调用静态成员函数
return 0;
}
类外访问不到私有的静态成员函数。