目录
引言
正文
01-构造函数和析构函数
02-构造函数的分类及调用
03-拷贝构造函数调用时机
04-构造函数调用规则
05-深拷贝与浅拷贝
06-初始化列表
07-静态成员变量
08-静态成员函数
总结
引言
在C++中,对象的初始化和清理是面向对象编程中非常重要的概念。对象的初始化是指在创建对象时对其进行初始化操作,而对象的清理则是指在对象不再需要时释放其所占用的资源和进行必要的清理操作。
(1)对象的初始化:
在C++中,对象的初始化可以通过构造函数来完成。构造函数是一种特殊的成员函数,它在对象创建时自动调用,用于对对象进行初始化。构造函数的名称与类名相同,没有返回值类型,可以有参数,包括默认参数。
(2)对象的清理:
对象的清理主要是指在对象不再需要时进行资源的释放和清理操作。在C++中,对象的清理通过析构函数来完成。析构函数是与构造函数相对应的一种特殊成员函数,用于在对象销毁时自动调用,进行清理操作。
正文
01-构造函数和析构函数
在C++中,构造函数和析构函数是面向对象编程中非常重要的概念,它们分别负责对象的初始化和清理工作。
(1)构造函数:
构造函数是一种特殊的成员函数,其主要作用是在对象创建时进行初始化操作。构造函数的名称与类名相同,没有返回类型,可以有参数,包括默认参数。在创建对象时,编译器会自动调用适当的构造函数。
构造函数可以分为以下几种类型:
a、默认构造函数:如果类没有显式定义构造函数,编译器会自动生成一个默认构造函数。默认构造函数不接受任何参数,用于创建对象时进行默认初始化操作。
b、带参数的构造函数:可以定义带参数的构造函数,用于在创建对象时提供初始值。通过构造函数参数的不同,可以实现对象的不同初始化方式。
c、复制构造函数:复制构造函数用于通过已存在的对象创建新对象,其参数为同类型的另一个对象的引用。复制构造函数在对象赋值、作为函数参数传递、以值方式返回时会被调用。
(2)析构函数:
析构函数是与构造函数相对应的一种特殊成员函数,其主要作用是在对象销毁时进行清理操作。析构函数的名称与类名相同,但在名称前面加上了波浪号(~),没有返回类型,不接受任何参数。在对象销毁时,编译器会自动调用适当的析构函数。
析构函数的作用主要包括释放对象所占用的资源、进行必要的清理操作等。
具体代码分析:
下面是一个简单的示例代码,展示了对象的初始化和清理过程,在这个示例中:
创建对象 p1
时调用了默认构造函数,对象 p2
时调用了带参数的构造函数,对象 p3
时调用了复制构造函数。
当 main
函数结束时,对象 p1
、p2
、p3
超出作用域,析构函数会被自动调用,释放对象所占用的资源。
示例展示了对象的初始化和清理过程,以及构造函数和析构函数的调用时机。通过合理地设计构造函数和析构函数,可以确保对象在创建和销毁时正确地进行初始化和清理,从而保证程序的稳定性和健壮性。
#include <iostream>
#include <string>
class Person {
private:
std::string name;
public:
// 默认构造函数
Person() {
std::cout << "Default constructor called" << std::endl;
}
// 带参数的构造函数
Person(const std::string& n) : name(n) {
std::cout << "Parameterized constructor called" << std::endl;
}
// 复制构造函数
Person(const Person& other) : name(other.name) {
std::cout << "Copy constructor called" << std::endl;
}
// 析构函数
~Person() {
std::cout << "Destructor called for " << name << std::endl;
}
};
int main() {
// 对象的初始化
Person p1; // 调用默认构造函数
Person p2("Alice"); // 调用带参数的构造函数
Person p3 = p2; // 调用复制构造函数
// 对象的清理
// 在 main 函数结束时,p1、p2、p3 超出作用域,析构函数会被调用,释放对象所占用的资源
return 0;
}
下面给出具体代码进行分析构造函数和析构函数的使用过程:
注:构造函数
/* 构造函数没有返回值,不用写void
函数命 与类名相同
构造函数可以有参数,可以发生重载
创建对象的时候,构造函数自动调用,而且只调用一次
*/
析构函数实现清理操作
/* 构造函数没有返回值,不用写void
函数命 与类名相同,但是要在名称前加 ~
构造函数不可以有参数,不可以发生重载
创建对象的时候,构造函数自动调用,而且只调用一次
*/
下面这个示例演示了一个简单的C++程序,其中定义了一个名为 Person
的类,该类包含了一个默认构造函数和一个析构函数。在 main
函数中创建了一个 Person
类的对象 p
,当对象 p
被创建时,构造函数被调用以完成对象的初始化,而当 main
函数结束时,对象 p
超出作用域,析构函数被调用以完成对象的清理。
#include <iostream>
using namespace std;
// 对象的初始化和清理
// 1、构造函数实现初始化操作
class Person
{
public:
Person()
{
cout << "Person构造函数的调用" << endl;
}
~Person()
{
cout << "Person析构函数的调用" << endl;
}
};
int main()
{
//test01();
Person p;
system("pause");
return 0;
}
示例运行结果如下图所示:
02-构造函数的分类及调用
构造函数可以根据其参数列表的不同分类,包括默认构造函数、带参数的构造函数和复制构造函数。下面是详细解释:
构造函数可以分为以下几种类型:
a、默认构造函数:如果类没有显式定义构造函数,编译器会自动生成一个默认构造函数。默认构造函数不接受任何参数,用于创建对象时进行默认初始化操作。
b、带参数的构造函数:可以定义带参数的构造函数,用于在创建对象时提供初始值。通过构造函数参数的不同,可以实现对象的不同初始化方式。
c、复制构造函数:复制构造函数用于通过已存在的对象创建新对象,其参数为同类型的另一个对象的引用。复制构造函数在对象赋值、作为函数参数传递、以值方式返回时会被调用
接下来给出具体代码分析构造函数的分类及调用过程应用,示例演示了C++中构造函数的分类及调用方式。详细解释代码如下:
(1)构造函数的分类:
a、根据参数分类:分为无参构造函数(默认构造函数)和有参构造函数。
b、根据类型分类:普通构造函数和拷贝构造函数。
(2)类Person
的定义:
a、定义了一个 Person
类,其中包括默认构造函数、有参构造函数以及拷贝构造函数。
b、默认构造函数用于无参初始化,有参构造函数用于接受一个整型参数并初始化对象的 age
成员,拷贝构造函数用于创建一个新对象并初始化为另一个对象的副本。
(3)调用:test01
函数中展示了不同的构造函数调用方式:
a、使用隐式转换法:通过将整型值隐式转换为 Person
对象来调用有参构造函数或拷贝构造函数。
b、使用显示法:通过显式地指定构造函数的调用方式来创建对象。
c、使用括号法:可以通过在括号内提供参数来调用有参构造函数或拷贝构造函数。
(4)注意事项:
a、在使用括号法调用默认构造函数时,不要加括号,否则编译器会将其误解为函数声明。避免
b、使用拷贝构造函数初始化匿名对象,因为编译器会将其误解为重定义。
#include <iostream>
using namespace std;
// 构造函数的分类及调用
// 分类
// 按照参数分类, 无参(也叫默认构造)和有参
// 按照类型分类 普通构造 和 拷贝构造
class Person
{
public:
// 构造函数
Person()
{
cout << "Person无参构造函数调用" << endl;
}
Person(int a)
{
age = a;
cout << "Person有参构造函数调用" << endl;
}
// 拷贝构造
Person(const Person &p) // const是防止拷贝时发生改变
{
// 将传入的参数,全部拷贝到该参数
cout << "Person拷贝构造函数调用" << endl;
age = p.age;
}
// 析构函数 无参数,也无重载
~Person()
{
cout << "Person析构函数调用" << endl;
}
int age ;
};
// 调用
void test01()
{
// 1、括号法 使用括号法最好
// Person p1; // 默认
// Person p2(10); // 有参
// Person p3(p2); // 拷贝
// 注意事项
/* 调用默认构造函数的时候,不要加()
因为,下各行代码,编译器会认为是一个函数的声明,不会认为在创建对象
Person p1(); 这句和函数声明 void func(); 比较相似,编译器会认为是声明
*/
// cout << "p2的年龄为: " << p2.age << endl;
// cout << "p3的年龄为: " << p3.age << endl;
// 2、显示法
Person p1; // 无参
Person p2 = Person(10); // 有参
Person p3 = Person(p2);
// 注意事项2
/* 不要利用拷贝构造函数初始化匿名对象
Person(p3); 编译器会认为 Person(p3) == Person p3 重定义
*/
// 3、隐式转换法
Person p4 = 10;
Person p5 = p4;
}
int main()
{
test01();
system("pause");
return 0;
}
示例运行结果如下:
03-拷贝构造函数调用时机
拷贝构造函数是一种特殊的构造函数,它在以下情况下被调用:
a、对象以值方式传递给函数:当一个对象作为参数传递给函数,并且函数的形参是按值传递时,会触发拷贝构造函数的调用,用于创建传递给函数的参数对象的副本。
b、对象以值方式返回函数:当一个函数返回一个对象时,并且函数的返回类型是非引用类型时,会触发拷贝构造函数的调用,用于创建函数返回的对象的副本。
c、通过赋值操作符进行对象赋值:当一个对象被另一个对象赋值时,会触发拷贝构造函数的调用,用于创建赋值目标对象的副本。
下面是一个具体的代码示例,演示拷贝构造函数的调用时机,在这个示例中,拷贝构造函数被调用的时机包括:
a、当对象 p1
以值方式传递给函数 printPerson
时,会触发拷贝构造函数的调用,创建 printPerson
函数中的参数对象的副本。
b、当函数 createPerson
返回一个 Person
对象时,会触发拷贝构造函数的调用,用于创建返回的对象的副本。
c、当对象 p2
被赋值给对象 p3
时,会触发拷贝构造函数的调用,用于创建 p3
对象的副本。
#include <iostream>
#include <string>
class Person {
private:
std::string name;
public:
// 构造函数
Person(const std::string& n) : name(n) {
std::cout << "Constructor called for name: " << name << std::endl;
}
// 拷贝构造函数
Person(const Person& other) : name(other.name) {
std::cout << "Copy constructor called for name: " << name << std::endl;
}
// 析构函数
~Person() {
std::cout << "Destructor called for name: " << name << std::endl;
}
};
// 函数以值方式返回对象
Person createPerson(const std::string& name) {
Person p(name);
return p;
}
// 函数以值方式接受对象参数
void printPerson(Person p) {
std::cout << "Printing person: " << p << std::endl;
}
int main() {
// 1. 对象以值方式传递给函数
Person p1("Alice");
printPerson(p1);
// 2. 对象以值方式返回函数
Person p2 = createPerson("Bob");
// 3. 通过赋值操作符进行对象赋值
Person p3 = p2;
return 0;
}
04-构造函数调用规则
构造函数的调用规则主要涵盖了对象的创建、初始化、拷贝以及销毁等方面。详细解释如下:
(1)对象的创建和初始化:
a、当创建一个对象时,会自动调用相应的构造函数来完成对象的初始化。
b、如果在类的定义中没有显式声明构造函数,则编译器会生成一个默认构造函数,用于对象的默认初始化。
c、如果定义了带参数的构造函数,则可以通过提供参数值来调用该构造函数,完成对象的定制化初始化。
d、对象的创建和初始化是构造函数最基本的调用规则之一。
(2)拷贝构造函数的调用:
a、拷贝构造函数用于创建一个对象的副本,通常在对象拷贝时被调用。
b、当一个对象以值方式传递给函数,以值方式返回函数,或者通过赋值操作符进行对象赋值时,都会触发拷贝构造函数的调用。
c、拷贝构造函数确保新对象与原对象具有相同的状态和数据。
d、对象的拷贝是保证程序正确性和数据完整性的重要环节。
(3)构造函数的重载和调用匹配:
a、在一个类中可以定义多个构造函数,它们可以根据参数的不同进行重载。
b、当创建对象时,编译器会根据提供的参数类型和数量匹配合适的构造函数进行调用。
c、如果参数匹配多个构造函数,编译器会选择最佳匹配的构造函数进行调用。
d、构造函数的重载和调用匹配确保了对象初始化的灵活性和多样性。
(4)析构函数的调用:
a、析构函数是对象生命周期的最后一个调用,用于完成对象的销毁和资源释放工作。
b、当对象超出其作用域、被显式删除或程序结束时,会自动调用析构函数。
c、析构函数的调用保证了程序的资源管理和内存释放。
默认情况下,c++编译器至少给一个类添加3个函数:1.默认构造函数(无参,函数体为空)
;2.默认析构函数(无参,函数体为空);3.默认拷贝构造函数,对属性进行值拷贝
/* 构造函数调用规则如下:
如果用户定义有参构造函数,并且未定义拷贝构造,c++不在提供默认无参构造,但是会提供默认拷贝构造
如果用户定义拷贝构造函数,c++不会再提供其他构造函数*/
定义有参构造,不提供默认,提供默认拷贝,也就是说如果不进行自己定义,不可能有默认有参构造函数
定义了拷贝构造,则默认构造有参构造都不会有,除非自己定义
下面是一个具体的代码示例,演示了构造函数调用规则:在这个示例中,通过创建对象并调用不同类型的构造函数,展示了构造函数的基本调用规则。同时,在对象的拷贝过程中,也展示了拷贝构造函数的调用规则和作用。
#include <iostream>
#include <string>
class Person {
private:
std::string name;
public:
// 默认构造函数
Person() {
std::cout << "Default constructor called" << std::endl;
}
// 带参数的构造函数
Person(const std::string& n) : name(n) {
std::cout << "Parameterized constructor called with name: " << name << std::endl;
}
// 拷贝构造函数
Person(const Person& other) : name(other.name) {
std::cout << "Copy constructor called for name: " << name << std::endl;
}
// 析构函数
~Person() {
std::cout << "Destructor called for name: " << name << std::endl;
}
};
int main() {
// 创建对象并调用构造函数
Person p1; // 默认构造函数
Person p2("Alice"); // 带参数的构造函数
// 拷贝构造函数的调用
Person p3 = p2; // 拷贝构造函数
return 0;
}
下面给出具体的代码进行分析构造函数调用规则的分析:这个示例展示了一个简单的 Person
类的定义,包括默认构造函数、有参构造函数和拷贝构造函数。在 test01
函数中,首先创建了一个 p
对象,并手动为其年龄赋值为 18,然后通过拷贝构造函数将 p
对象的副本传递给 p2
对象。最后输出了 p2
对象的年龄为 18。这个示例说明了构造函数在对象创建和初始化过程中的重要性,以及拷贝构造函数在对象拷贝过程中的作用。
#include <iostream>
using namespace std;
class Person
{
public:
// 默认构造
Person()
{
cout << "Person默认构造函数调用" << endl;
}
// 有参构造
Person(int age)
{
m_Age = age;
cout << "Person有参构造函数调用" << endl;
}
// 拷贝构造
Person(const Person &p)
{
m_Age = p.m_Age;
cout << "Person拷贝构造函数调用" << endl;
}
// 析构构造
~Person()
{
cout << "Person析构构造函数调用" << endl;
}
int m_Age;
};
void test01()
{
Person p;
p.m_Age = 18;
Person p2(p);
cout << "p2的年龄为: " << p2.m_Age<<endl;
}
int main()
{
test01();
system("pause");
return 0;
}
示例运行结果如下图所示:
05-深拷贝与浅拷贝
深拷贝和浅拷贝都是对象拷贝的概念,但它们在内存管理和对象复制方面有着不同的表现:
浅拷贝:
浅拷贝只是简单地复制对象的成员变量的值,如果成员变量是指针类型,那么仅复制指针的地址,而不复制指针指向的内容。
这意味着,如果原对象和副本对象共享同一块内存区域,一旦其中一个对象的成员变量发生改变,另一个对象的对应成员变量也会受到影响。
浅拷贝通常会导致对象间的数据共享和潜在的数据安全性问题。
深拷贝:
深拷贝会复制对象的所有成员变量,包括指针类型的成员变量,同时也会复制指针所指向的内容,从而创建一个全新的对象及其数据。
这意味着,原对象和副本对象之间完全独立,对一个对象的操作不会影响另一个对象。
深拷贝通常用于需要独立、独立管理数据的情况,确保对象间的数据隔离和安全性。
下面是一个具体的代码示例,演示了浅拷贝和深拷贝的区别:在这个示例中,通过 String
类演示了浅拷贝和深拷贝的区别。通过在拷贝构造函数和深拷贝函数中输出相关信息,可以清晰地看到它们的不同行为。
#include <iostream>
#include <cstring>
class String {
private:
char *str;
public:
// 构造函数
String(const char *s = nullptr) {
if (s == nullptr) {
str = new char[1];
*str = '\0';
} else {
str = new char[strlen(s) + 1];
strcpy(str, s);
}
std::cout << "Constructor called: " << str << std::endl;
}
// 拷贝构造函数(浅拷贝)
String(const String &s) {
str = new char[strlen(s.str) + 1];
strcpy(str, s.str);
std::cout << "Copy constructor called: " << str << std::endl;
}
// 深拷贝
String deepCopy(const String &s) {
str = new char[strlen(s.str) + 1];
strcpy(str, s.str);
std::cout << "Deep copy constructor called: " << str << std::endl;
return *this;
}
// 析构函数
~String() {
delete[] str;
std::cout << "Destructor called: " << str << std::endl;
}
// 打印字符串
void print() const {
std::cout << str << std::endl;
}
};
int main() {
// 浅拷贝示例
String s1("Hello");
String s2 = s1; // 浅拷贝
std::cout << "String s1: ";
s1.print();
std::cout << "String s2: ";
s2.print();
// 深拷贝示例
String s3("World");
String s4;
s4.deepCopy(s3); // 深拷贝
std::cout << "String s3: ";
s3.print();
std::cout << "String s4: ";
s4.print();
return 0;
}
下面给出具体代码示例进行分析深拷贝与浅拷贝之间的关系,这个示例演示了深拷贝与浅拷贝的区别,并解释了为什么需要在类中实现自定义的拷贝构造函数。
Person
类的定义:
定义了一个 Person
类,包括默认构造函数、有参构造函数和自定义的拷贝构造函数。
在有参构造函数中,除了初始化年龄外,还动态分配了堆内存来存储身高信息。
test01
函数:
在 test01
函数中,首先创建了一个名为 p1
的 Person
对象,并手动为其年龄和身高赋值。
然后通过拷贝构造函数将 p1
对象的副本传递给 p2
对象。
最后输出了 p1
和 p2
对象的年龄和身高。
析构函数的重要性:
在 Person
类的析构函数中,释放了动态分配的内存。这一步是非常重要的,因为动态分配的内存需要手动释放,以防止内存泄漏。
浅拷贝带来的问题:
如果只是简单地使用编译器提供的默认拷贝构造函数(即浅拷贝),则会导致两个对象共享同一块内存,可能会出现重复释放内存的问题。
解决方法:
通过自定义拷贝构造函数实现深拷贝,即重新分配内存并将数据复制到新的内存地址,确保对象之间的数据独立性和内存安全性。
#include <iostream>
using namespace std;
// 深拷贝与浅拷贝 面试常问
class Person
{
public:
// 默认构造
Person()
{
cout << "Person默认构造函数调用" << endl;
}
// 有参构造
Person(int age,int height)
{
m_Age = age;
m_Height = new int(height); // new int(height)这句代码返回的就是 int* ,
// 因为已经定义了int *m_Height,因此可以使用m_Height等于开辟的新的内存
// 堆区开辟的数据,需要使用delete手动释放
cout << "Person有参构造函数调用" << endl;
}
// 自定义拷贝构造
Person(const Person &p)
{
cout << "Person拷贝构造函数调用" << endl;
m_Age = p.m_Age;
//m_Height = p.m_Height; //这句代码就是编译器自己写出的,只是完成了一个简单的浅拷贝,因为是指针,容易出现问题
// 深拷贝 重新开辟内存空间
m_Height = new int(*p.m_Height);
}
// 析构函数 用途通常是堆区开辟的数据做释放操作
~Person()
{
if (m_Height != NULL)
{
delete m_Height;
m_Height = NULL; // 防止野指针的出现,赋给空值
}
/* 这个时候释放堆区数据时,编译器无法执行到析构函数,原因如下
1、这里利用的是编译器提供的拷贝构造函数,Person p2(p1);这句只进行了浅拷贝,逐字节拷贝
2、出现错误主要在定义的指针身高那里,拷贝的时候,地址也直接拷贝过去,也就是说p1和p2这一块的地址是相同的
3、当执行析构时,p1和p2都会执行清洗,释放内存操作
4、栈有个规则就是先进后出,因此先进行p2内存的释放,而当p1也执行析构的时候,内存已经释放干净,
若是再进行释放,已经是非法操作 ,
因此浅拷贝带来的最大的问题,堆区的内存重复释放
解决办法
1、使用深拷贝解决该问题,就是说不使用编译器的拷贝构造函数,重新定义一个拷贝函数
也就是重新定义一段内存,这样的话,虽然内存地址不同,但是内存指向的地址是相同的
*/
cout << "Person析构函数调用" << endl;
}
int m_Age; //年龄
int *m_Height; // 使用指针,是为了将内存开辟到堆区,使用new+数据类型的方法
};
void test01()
{
Person p1(18,160); // 这里使用有参构造函数
cout << "p1的年龄为: " << p1.m_Age <<" 身高为: "<< *p1.m_Height<< endl;
Person p2(p1); // 拷贝构造函数
cout << "p2的年龄为: " << p2.m_Age << " 身高为: " << *p2.m_Height << endl; // 这里虽然没有使用到我们定义的拷贝构造函数,
// 但是编译器会进行一个浅拷贝的操作,也就是值的复制。
}
int main()
{
test01();
system("pause");
return 0;
}
示例运行结果如下:
06-初始化列表
初始化列表是C++中用于在构造函数中初始化类成员变量的一种方式。它通过在构造函数的参数列表后面使用冒号(:
)和一系列初始化操作来实现。
在构造函数体执行之前,初始化列表会先执行,用于对成员变量进行初始化。这种方式有时比在构造函数体中赋值更高效,并且可以处理const成员变量和引用类型的成员变量。
下面是一个简单的示例,演示了初始化列表的用法:在示例中,构造函数MyClass(int a, int b)
使用了初始化列表来初始化成员变量x
和y
,而不是在构造函数体内部进行赋值操作。
#include <iostream>
class MyClass {
private:
int x;
int y;
public:
// 构造函数使用初始化列表
MyClass(int a, int b) : x(a), y(b) {
// 构造函数体
// 这里可以执行额外的初始化或其他操作
}
void print() {
std::cout << "x: " << x << ", y: " << y << std::endl;
}
};
int main() {
// 创建对象并初始化
MyClass obj(5, 10);
obj.print();
return 0;
}
下面给出具体代码分析初始化列表的用法,这个示例演示了在C++中使用初始化列表来初始化类成员变量的方法。
(1)Person
类的定义:
Person
类有三个私有成员变量 m_A
、m_B
和 m_C
,分别表示整型数据。
(2)
构造函数的初始化列表:
在 Person
类的构造函数中,使用了初始化列表来对成员变量进行初始化。
通过在构造函数参数列表后使用冒号(:
)并按照成员变量的声明顺序进行初始化,将参数值传递给对应的成员变量。这样的方式更加简洁和高效,比传统的在构造函数体内逐个赋值要好。
(3)PrintPerson
函数:
PrintPerson
函数用于打印 Person
对象的成员变量值。
(4)
在 main
函数中创建对象:
在 main
函数中创建了一个 Person
对象 p
,并传入三个参数来初始化对象的成员变量。
然后调用对象的 PrintPerson
函数,打印出成员变量的值。
#include<iostream>
using namespace std;
class Person {
public:
传统方式初始化
//Person(int a, int b, int c) {
// m_A = a;
// m_B = b;
// m_C = c;
//}
//初始化列表方式初始化
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
void PrintPerson() {
cout << "mA:" << m_A << endl;
cout << "mB:" << m_B << endl;
cout << "mC:" << m_C << endl;
}
private:
int m_A;
int m_B;
int m_C;
};
int main()
{
Person p(1, 2, 3);
p.PrintPerson();
system("pause");
return 0;
}
示例运行结果如下图所示:
07-静态成员变量
静态成员变量是属于类本身而不是类的实例的成员变量。它与类的每个实例无关,而是与整个类相关联。静态成员变量在类的所有实例之间是共享的,可以被类的所有实例访问和修改。通常情况下,静态成员变量用于表示类级别的属性,例如类的计数器、全局配置等。
下面是一个简单的示例代码来解释静态成员变量的用法:在这个示例中,MyClass
类有一个静态成员变量 count
,表示创建的 MyClass
对象的数量。在类外部需要使用 MyClass::count
来声明并初始化静态成员变量。每当创建一个 MyClass
对象时,构造函数会将 count
的值增加1。printCount()
是一个静态成员函数,用于打印当前创建的对象数量。
#include <iostream>
class MyClass {
public:
// 静态成员变量
static int count;
// 构造函数
MyClass() {
count++; // 每创建一个对象,count增加1
}
// 打印对象数量的静态成员函数
static void printCount() {
std::cout << "Number of objects created: " << count << std::endl;
}
};
// 初始化静态成员变量
int MyClass::count = 0;
int main() {
// 创建多个对象
MyClass obj1;
MyClass obj2;
MyClass obj3;
// 调用静态成员函数打印对象数量
MyClass::printCount();
return 0;
}
下面给出具体的代码分析静态成员变量的应用过程,这个示例演示了静态成员变量的用法及其特点,代码具体解释如下:
(1)Person
类的定义:
Person
类中包含两个静态成员变量 m_A
和 m_B
,它们都属于类而不是类的实例。静态成员变量在类内部声明,但在类外部初始化。在这个示例中,m_A
被初始化为 100,m_B
被初始化为 200。
(2)
静态成员变量的特点:
所有对象共享同一份数据:无论创建多少个 Person
类的对象,它们都共享同一个 m_A
,即修改一个对象的 m_A
会影响所有其他对象的 m_A
。
编译阶段分配内存:静态成员变量在编译阶段就会分配内存,而不是在对象创建时。
类内声明,类外初始化操作:静态成员变量在类内部声明,但在类外部初始化。
(3)
访问静态成员变量:
可以通过类的对象或者类名来访问静态成员变量。在示例中,test01()
函数展示了通过对象进行访问,而 test02()
函数展示了通过类名进行访问。
#include<iostream>
using namespace std;
// 静态成员变量
class Person
{
public:
// 1、所有对象都共享同一份数据
// 2、编译阶段就分配内存
// 3、类内声明,类外初始化操作
static int m_A; // 这是属于类内声明
// 静态成员变量也是有访问权限的
private:
static int m_B;
};
int Person::m_A = 100; // 这样就是指的是类外初始化,并且在Person作用下的变量
int Person::m_B = 200; // 后面使用函数在类外是无法访问的
void test01()
{
Person p;
cout << p.m_A << endl;
Person p1;
p1.m_A = 200;
cout << p.m_A << endl; // 此时m_A变成200,所有对象(即Person下创建的对象)都共享一份数据
}
void test02()
{
// 两种访问方式
// 1、通过对象进行访问
// Person p;
// cout << p.m_A << endl;
// 2、通过类名进行访问
cout << Person::m_A << endl;
}
int main()
{
//test01();
test02();
system("pause");
return 0;
}
示例运行结果如下图所示:
08-静态成员函数
静态成员函数与静态成员变量类似,它们也属于类本身而不是类的实例。静态成员函数不依赖于任何特定的实例对象,它们可以直接通过类名来调用,而不需要通过对象。通常情况下,静态成员函数用于执行与类相关的操作,而不需要访问特定对象的状态。
下面是一个简单的示例代码来解释静态成员函数的用法:
在这个示例中,MyClass
类有一个静态成员函数 staticFunction()
和一个非静态成员函数 normalFunction()
。静态成员函数可以直接通过类名 MyClass::staticFunction()
来调用,而非静态成员函数需要通过对象 obj.normalFunction()
来调用。
静态成员函数通常用于执行与类相关的操作,例如设置或获取类级别的属性,而非静态成员函数通常用于操作特定对象的状态。
#include <iostream>
class MyClass {
public:
// 静态成员函数
static void staticFunction() {
std::cout << "This is a static member function." << std::endl;
}
// 非静态成员函数
void normalFunction() {
std::cout << "This is a normal member function." << std::endl;
}
};
int main() {
// 调用静态成员函数
MyClass::staticFunction();
// 创建对象
MyClass obj;
// 调用非静态成员函数
obj.normalFunction();
return 0;
}
下面给出具体代码进行分析静态成员函数的应用过程,这个示例展示了静态成员函数的用法及其特点:
(1)静态成员函数的定义:
Person
类中定义了一个静态成员函数 func()
,它属于类而不是类的实例。静态成员函数不依赖于特定的对象实例,因此可以通过类名或对象来调用。
静态成员函数可以直接访问静态成员变量 m_A
,但无法直接访问非静态成员变量 m_B
。
(2)特点:
所有对象共享同一个函数:无论创建多少个 Person
类的对象,它们都共享同一个 func()
函数。
静态成员函数只能访问静态成员变量:在 func()
中可以直接访问静态成员变量 m_A
,但不能直接访问非静态成员变量 m_B
。
(3)调用方式:
可以通过对象来调用静态成员函数,例如 p.func()
。
也可以通过类名来调用静态成员函数,例如 Person::func()
,这是因为静态成员函数不依赖于特定的对象实例。
#include<iostream>
using namespace std;
// 静态成员函数
// 1、所有对象都共享同一个函数
// 2、静态成员函数只能访问静态成员变量
class Person
{
public:
static void func()
{
m_A = 200; // 这里是可以进行更改的
cout << "static void func调用" << endl;
}
static int m_A; //静态成员变量
int m_B; // 非静态成员变量,是无法进行访问的
private:
// 静态成员函数也有访问权限 ,func2类外是无法访问的
static void func2()
{
}
};
int Person::m_A = 100;
void test01()
{
// 1、通过对象进行访问
Person p;
p.func();
// 2、通过类名访问
Person::func(); // 因为该静态函数不属于某一个对象上,因此通过类名也可以进行访问
}
int main()
{
test01();
system("pause");
return 0;
}
示例运行结果如下图所示:
总结
经过上述分析,在C++中,类和对象的初始化和清理是面向对象编程中非常重要的概念。以下是关于C++类和对象初始化和清理的总结:
对象的初始化:
默认构造函数:如果在类中没有定义任何构造函数,C++会提供一个默认构造函数。默认构造函数没有参数,用于创建对象时执行初始化操作。
自定义构造函数:类可以定义一个或多个自定义构造函数,用于根据不同的参数进行对象的初始化。构造函数可以重载,允许根据参数的不同进行不同的初始化操作。
成员初始化列表:构造函数可以使用成员初始化列表来初始化类的成员变量,而不是在函数体中进行赋值操作。使用成员初始化列表可以提高效率,并且在初始化常量成员或引用成员时是必须的。
拷贝构造函数:拷贝构造函数用于在创建对象时,以另一个同类对象作为参数进行初始化。如果没有显式定义拷贝构造函数,编译器会提供一个默认的浅拷贝构造函数。
对象的清理:
析构函数:析构函数是类的特殊成员函数,用于在对象生命周期结束时执行清理操作。析构函数的名称与类名相同,前面加上波浪号(~)作为前缀。当对象被销毁时(例如离开作用域、delete 操作、程序结束等),析构函数会自动调用。
显式清理资源:如果类中使用了动态分配的资源(如堆内存、文件句柄等),需要在析构函数中显式清理这些资源,以避免内存泄漏或资源泄漏。
深拷贝和浅拷贝:当类中包含指针成员时,需要注意拷贝构造函数和赋值运算符的实现,以避免浅拷贝导致的资源释放问题。
移动语义(C++11):C++11引入了移动语义,通过移动构造函数和移动赋值运算符可以实现对资源的高效转移,提高性能。
总的来说,对象的初始化和清理是面向对象编程中的重要概念,正确地管理对象的生命周期可以确保程序的正确性和性能。