[C++核心编程-05]----C++类和对象之对象的初始化和清理

目录

引言           

正文           

01-构造函数和析构函数        

​02-构造函数的分类及调用       

03-拷贝构造函数调用时机       

04-构造函数调用规则       

05-深拷贝与浅拷贝       

06-初始化列表      

07-静态成员变量      

08-静态成员函数      

总结           


引言           

        在C++中,对象的初始化和清理是面向对象编程中非常重要的概念。对象的初始化是指在创建对象时对其进行初始化操作,而对象的清理则是指在对象不再需要时释放其所占用的资源和进行必要的清理操作。

        (1)对象的初始化:

        在C++中,对象的初始化可以通过构造函数来完成。构造函数是一种特殊的成员函数,它在对象创建时自动调用,用于对对象进行初始化。构造函数的名称与类名相同,没有返回值类型,可以有参数,包括默认参数。

        (2)对象的清理:

        对象的清理主要是指在对象不再需要时进行资源的释放和清理操作。在C++中,对象的清理通过析构函数来完成。析构函数是与构造函数相对应的一种特殊成员函数,用于在对象销毁时自动调用,进行清理操作。

正文           

01-构造函数和析构函数        

        在C++中,构造函数和析构函数是面向对象编程中非常重要的概念,它们分别负责对象的初始化和清理工作。

        (1)构造函数:

        构造函数是一种特殊的成员函数,其主要作用是在对象创建时进行初始化操作。构造函数的名称与类名相同,没有返回类型,可以有参数,包括默认参数。在创建对象时,编译器会自动调用适当的构造函数。

        构造函数可以分为以下几种类型:

        a、默认构造函数:如果类没有显式定义构造函数,编译器会自动生成一个默认构造函数。默认构造函数不接受任何参数,用于创建对象时进行默认初始化操作。

        b、带参数的构造函数:可以定义带参数的构造函数,用于在创建对象时提供初始值。通过构造函数参数的不同,可以实现对象的不同初始化方式。

        c、复制构造函数:复制构造函数用于通过已存在的对象创建新对象,其参数为同类型的另一个对象的引用。复制构造函数在对象赋值、作为函数参数传递、以值方式返回时会被调用。

        (2)析构函数:

        析构函数是与构造函数相对应的一种特殊成员函数,其主要作用是在对象销毁时进行清理操作。析构函数的名称与类名相同,但在名称前面加上了波浪号(~),没有返回类型,不接受任何参数。在对象销毁时,编译器会自动调用适当的析构函数。

        析构函数的作用主要包括释放对象所占用的资源、进行必要的清理操作等。

        具体代码分析:

        下面是一个简单的示例代码,展示了对象的初始化和清理过程,在这个示例中:

        创建对象 p1 时调用了默认构造函数,对象 p2 时调用了带参数的构造函数,对象 p3 时调用了复制构造函数。

        当 main 函数结束时,对象 p1p2p3 超出作用域,析构函数会被自动调用,释放对象所占用的资源。

        示例展示了对象的初始化和清理过程,以及构造函数和析构函数的调用时机。通过合理地设计构造函数和析构函数,可以确保对象在创建和销毁时正确地进行初始化和清理,从而保证程序的稳定性和健壮性。

#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)使用了初始化列表来初始化成员变量xy,而不是在构造函数体内部进行赋值操作。

#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_Am_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引入了移动语义,通过移动构造函数和移动赋值运算符可以实现对资源的高效转移,提高性能。

        总的来说,对象的初始化和清理是面向对象编程中的重要概念,正确地管理对象的生命周期可以确保程序的正确性和性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/608404.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Eigen求解线性方程组

1、线性方程组的应用 线性方程组可以用来解决各种涉及线性关系的问题。以下是一些通常可以用线性方程组来解决的问题&#xff1a; 在实际工程和科学计算中&#xff0c;求解多项式方程的根有着广泛的应用。 在控制系统的设计中&#xff0c;我们经常需要求解特征方程的根来分析…

【训练与预测】02 - 完整的模型验证套路

02 - 完整的模型验证套路 模型图 验证一个模型就是指使用已经训练好的模型&#xff0c;然后给它提供输入。 test.py import torch import torchvision from PIL import Imagedevice torch.device("cuda" if torch.cuda.is_available() else "cpu") ima…

C++学习笔记——仿函数

文章目录 仿函数——思维导图仿函数是什么仿函数的优势理解仿函数仿函数的原理举例 仿函数——思维导图 仿函数是什么 使用对象名调用operator&#xff08;&#xff09;函数看起来像是在使用函数一样&#xff0c;因此便有了仿函数的称呼&#xff1b;仿函数存在的意义是&#x…

Burp Suite 抓包,浏览器提示有软件正在阻止Firefox安全地连接到此网站

问题现象 有软件正在阻止Firefox安全地连接到此网站 解决办法 没有安装证书&#xff0c;在浏览器里面安装bp的证书就可以了 参考&#xff1a;教程合集 《H01-启动和激活Burp.docx》——第5步

如何防止源代码泄露?彻底解决源代码防泄密的方法

SDC沙盒系统&#xff1a;数据安全的守护者 SDC沙盒系统&#xff0c;为研发型企业设计&#xff0c;实现了对数据的代码级保护&#xff0c;同时不影响工作效率和正常使用。系统通过自动加密敏感数据&#xff0c;并配合多种管控机制&#xff0c;有效防止了数据的泄露。 涉密可信…

代码随想录算法训练营第四十二天| 01背包问题(二维、一维)、416.分割等和子集

系列文章目录 目录 系列文章目录动态规划&#xff1a;01背包理论基础①二维数组②一维数组&#xff08;滚动数组&#xff09; 416. 分割等和子集①回溯法&#xff08;超时&#xff09;②动态规划&#xff08;01背包&#xff09;未剪枝版剪枝版 动态规划&#xff1a;01背包理论基…

渗透之sql注入----二次注入

目录 二次注入的原理&#xff1a; 实战&#xff1a; 第一步&#xff1a;找注入点 找漏洞&#xff1a; 注入大概过程&#xff1a; 第二步&#xff1a;开始注入 二次注入的原理&#xff1a; 二次注入是由于对用户输入的数据过滤不严谨&#xff0c;导致存在异常的数据被出入…

FreeRTOS的任务详解、创建与删除

目录 1、任务详解 1.1 什么是任务&#xff1f; 1.2 任务的特点 1.3 任务的状态 1.4 任务的优先级 1.5 任务的堆和栈 2、任务的创建与删除 2.1 相关API 2.2 函数解析 2.2.1 xTaxkCreate() 2.2.2 xTaskCreateStatic() 2.2.3 vTaskDelete() 3、实战案例 3.1 创建两个…

达梦数据刷盘测试

达梦数据库为了保证数据故障恢复的一致性&#xff0c;REDO 日志的刷盘必须在数据页刷盘之前进行。 下面我们通过测试来验证是不是这样 执行我们事先准备的SHELL脚本 可以看到第一次strings文件没有输出&#xff0c;说明刚写的数据在数据库的BUFFER缓冲区内&#xff0c;还没有刷…

RN阴影组件使用

yarn add react-native-shadow yarn add react-native-svg // 这个是必须的,阴影依赖这个包四周都有阴影,如下设置 import React from react; import {StyleSheet, View, Text} from react-native; import {BoxShadow} from react-native-shadow;const App () > {const …

3GBJ5016A-ASEMI电焊机专用3GBJ5016A

编辑&#xff1a;ll 3GBJ5016A-ASEMI电焊机专用3GBJ5016A 型号&#xff1a;3GBJ5016A 品牌&#xff1a;ASEMI 封装&#xff1a;3GBJ-5 正向电流&#xff08;Id&#xff09;&#xff1a;50A 反向耐压&#xff08;VRRM&#xff09;&#xff1a;1600V 正向浪涌电流&#xf…

“找不到mfcm80u.dll”错误怎么办?一文了解原因和解决办法!

在使用Windows操作系统时&#xff0c;许多用户可能会遇到各种DLL文件缺失或损坏的问题。其中&#xff0c;“找不到mfc80u.dll”错误就是比较常见的一种。 下面小编就给大家分享出现“找不到mfc80u.dll”错误的原因和解决办法&#xff0c;帮助您快速解决此问题。 一、mfc80u.dl…

漏洞管理是如何在攻击者之前识别漏洞从而帮助人们阻止攻击的

漏洞管理 是主动查找、评估和缓解组织 IT 环境中的安全漏洞、弱点、差距、错误配置和错误的过程。该过程通常扩展到整个 IT 环境&#xff0c;包括网络、应用程序、系统、基础设施、软件和第三方服务等。鉴于所涉及的高成本&#xff0c;组织根本无法承受网络攻击和数据泄露。如果…

mysql数据库调优篇章1

目录 1.认识数据库中日志的作用2.增加mysql数据库中my.ini 基本配置3.增加my.ini中参数配置4.查看已经执行过的sql语句过去执行时间5.找出慢查询的sql6. SHOW VARIABLES LIKE ‘innodb_read_io_threads’; SHOW VARIABLES LIKE ‘innodb_write_io_threads’; SHOW VARIABLES LI…

森林消防—高扬程水泵,高效、稳定、可靠!/恒峰智慧科技

森林&#xff0c;作为地球的“绿色肺叶”&#xff0c;不仅为我们提供了丰富的自然资源&#xff0c;更是维持生态平衡的重要一环。然而&#xff0c;随着全球气候的变化和人为活动的增加&#xff0c;森林火灾频发&#xff0c;给生态环境和人民生命财产安全带来了巨大威胁。在森林…

17 空闲空间管理

目录 假设 底层机制 分割与合并 追踪已分配空间的大小 嵌入空闲列表 让堆增长 基本策略 最优匹配 首次匹配 下次匹配 其他方式 分离空闲列表 伙伴系统 小结 分页是将内存成大小相等的内存块&#xff0c;这样的机制下面&#xff0c;很容易去管理这些内存&#xff0c…

代码随想录Day 37|Leetcode|Python|● 1049. 最后一块石头的重量 II ● 494. 目标和 ● 474.一和零

1049. 最后一块石头的重量 II 有一堆石头&#xff0c;用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合&#xff0c;从中选出任意两块石头&#xff0c;然后将它们一起粉碎。假设石头的重量分别为 x 和 y&#xff0c;且 x < y。那么粉碎的可能结…

【C++】详解STL容器之一的deque和适配器stack,queue

目录 deque的概述 deque空间的结构 deque的迭代器 deque的数据设计 deque的优缺点 适配器的概念 ​编辑 stack的概述 stack的模拟实现 queue的概述 queue的模拟实现 deque的概述 deque的设计参考了另外两大容器vector和list。可参考下面两篇文章 详解vector&#x…

Java 语法 (杂七杂八的知识)

面向对象三大特性 封装, 多态, 继承 基本数据类型 一字节 (Byte) 占八位 (bit) JDK, JRE, JVM JDK (Java Development Kit) : Java 开发工具包, 包括了 JRE, 编译器 javac, 和调试工具 Jconsole, jstack 等 JRE (Java Runtime Environment) : Java 运行时环境, 包括了 JVM , …

ssm115乐购游戏商城系统+vue

毕业生学历证明系统 设计与实现 内容摘要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统毕业生学历信息管理难…