C++核心编程学习--对象特性--对象的初始化和清理

发布于:2025-07-25 ⋅ 阅读:(24) ⋅ 点赞:(0)

构造函数和析构函数

构造函数用于初始化
析构函数用于清理
编译器会自动调用这两个函数,完成对象初始化和清理工作。
如果程序员不提供构造和析构,编译器会提供,但是两个函数是空实现。
构造函数:主要用于创建对象时,为对象的成员属性赋值。
析构函数:主要用于对象销毁时系统自动调用,执行一些清理工作。

构造函数的语法: 类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候回自动调用构造,无需手动调用,且只会调用一次。

析构函数的语法: ~类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同加上~
3.构造函数不可以有参数,因此不可以发生重载
4.程序在销毁对象时候回自动调用析构,无需手动调用,且只会调用一次。

#include<iostream>
using namespace std;
#include<string>

class Person{
public:
	Person() {
	
		cout << "person的构造函数调用" << endl;
}
	~Person() {
		cout << "person的析构函数调用" << endl;
	}

private:
	string name;
	int age;
};

int main() {
	Person stj;
	system("pause");
	return 0;
}

构造函数的分类和调用

两种分类:
有参构造(默认构造)和无参构造
按类型分为:
普通构造和拷贝构造
将对象的属性全部拷贝。

#include <iostream>
using namespace std;

// 构造函数按参数分类为: 1.无参构造函数 2.有参构造函数
// 按类型分类为: 1.默认构造函数 2.拷贝构造函数
class Person {
public:
    // 无参(默认)构造函数
    Person() {
        cout << "无参构造函数" << endl;
    }

    // 有参构造函数
    Person(int a) {
        age = a;
        cout << "有参构造函数" << endl;
    }

    // 拷贝构造函数
    Person(const Person& p) {  // Person(const Person& P)
        // 将传入的对象的所有属性值拷贝到当前对象中
        age = p.age;
        cout << "拷贝构造函数" << endl;
    }
    // 析构函数
    ~Person() {
        cout << "析构函数" << endl;
    }
public:
    int age;
};

// 构造函数的调用
// 调用无参构造函数
void test1() {
    Person p;
}

// 调用有参构造函数
void test2() {

    // 1.括号法:常用
    Person p0;      // 调用无参(默认)构造函数
    Person p1(10);   // 调用有参构造函数
    Person p2(p1);      //  调用拷贝构造函数
    // 注意1:调用无参构造函数不能带括号,例如:Person p2(),因为编译器会认为这是一个函数声明,不会认为在创建对象

    // 测试调用是否成功
    cout << "p1的年龄为:" << p1.age << endl;
    cout << "p2的年龄为:" << p2.age << endl;

    // 2.显式法
    Person p3 = Person(10);     // 调用有参构造函数
    Person p4 = Person(p3);     // 调用拷贝构造函数
    // 注意2:Person(10)可以放在等号右边,但是单独写是匿名对象,匿名对象的特点是,当前行执行结束后,匿名对象就销毁了
    // 注意3: 不要利用拷贝构造函数初始化匿名对象,错误写法:Person(p3); 编译器会认为Person(p3) == Person p3 重定义了

    // 3.隐式转换法
    Person p5 = 10;     // 等价于 Person p4 = Person(10);
    Person p6 = p5;     // 拷贝构造

    // 注意:不能利用拷贝构造函数初始化匿名对象,因为匿名对象没有名字,编译器会认为是对象声明。错误写法:Person p5(p4)
}

int main()
{
    test1();
    cout << "-------------" << endl;
    test2();
    return 0;
}

拷贝函数的调用时机
1.使用一个已经创建完毕的对象来初始化一个新对象
2.值传递的方式给函数传参数
3.值方式返回局部对象
如果自己没有定义拷贝构造函数,在上述操作中,编译器会自动创建一个拷贝构造函数,实现临时对象的创建。相当于函数中的临时副本、临时变量。

#include <iostream>
using namespace std;

class Person {
public:
    Person() {
        cout << "无参构造函数!" << endl;
        mAge = 0;
    }
    Person(int age) {
        cout << "有参构造函数!" << endl;
        mAge = age;
    }
    Person(const Person& p) {
        cout << "拷贝构造函数!" << endl;
        mAge = p.mAge;
    }
    //析构函数在释放内存之前调用
    ~Person() {
        cout << "析构函数!" << endl;
    }
public:
    int mAge;
};

// 1.使用一个已经初始化的对象来初始化一个对象
void test1() {

    Person man(100);    // 创建一个对象
    Person newman(man);     // 调用拷贝构造函数
    Person newman2 = man;   //  调用拷贝构造函数
}

// 2.值传递的方式给函数参数传值    这里在dowork中修改某个属性,原P中的属性不会改变,因为是临时拷贝
void doWork(Person p){}
void test2() {
    Person p;
    doWork(p);
}

// 3.值方式返回局部对象
Person doWork2() {
    Person p1;
    return p1;
}
void test3() {
    Person p = doWork2();
}
int main() {

    test1();
    test2();
    test3();
    return 0;
}

构造函数的调用规则
默认情况下,C++编译器至少给一个类添加三个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
如果已经自定义了某种构造函数,比如说有参的,那么编译器就不会提供相应的构造函数了,再进行无参构造就会报错。但是会默认提供拷贝构造函数
如果只自定义了拷贝构造函数,编译器也不会自动创建无参和有参构造函数。

#include <iostream>
using namespace std;

class Person {
public:
    //无参(默认)构造函数
    Person() {
        cout << "无参构造函数!" << endl;
    }
    //有参构造函数
    Person(int a) {
        age = a;
        cout << "有参构造函数!" << endl;
    }
    //拷贝构造函数
    Person (const Person &p) {
        age = p.age;
        cout << "拷贝构造函数!" << endl;
    }
    // 析构函数
    ~Person() {
        cout << "析构函数!" << endl;
    }

public:
    int age;
};

void test1() {
    Person p1(18);
    // 如果不写拷贝构造,编译器会自动添加拷贝构造函数,并做浅拷贝操作
    Person p2 = p1;

    cout << "p2的年龄为:" << p2.age << endl;
}

void test2() {
    // 如果用户提供有参构造,编译器不会提供默认构造函数,会提出拷贝构造
    Person p1;      //此时如果用户没有提供无参构造,编译器会报错
    Person p2(10);      // 此时如果用户没有提供有参构造,编译器会报错
    Person p3(p2);      //  此时如果用户没有提供拷贝构造,编译器会提供

    // 如果用户提供拷贝构造,编译器不会提供其他构造函数
    Person p4;      // 此时如果用户没有提供无参构造,编译器会报错
    Person p5(10);     //  此时如果用户没有提供有参构造,编译器会报错
    Person p6(p5);      // 此时如果用户没有提供拷贝构造,编译器会报错
}
int main() {

    test1();
    test2();
    system("pause");
    return 0;
}

深拷贝和浅拷贝

浅拷贝:简单的赋值操作
如果使用默认的拷贝函数,会浅拷贝,逐字节的将对象拷贝到另一个对象上(相当于直接赋值)
如果已经在栈上创建了两个对象,也就是局部函数中创建的对象,并且执行默认的拷贝构造函数,那么会进行浅拷贝,这时如果在堆区开辟数据,那么就会把堆区指针直接拷贝过去,最终析构的时候由于(栈区:先进后出,先创建的后释放)会逐个释放,释放两次堆区的数据,所以导致堆区重复释放。
所以需要自定义的拷贝构造函数,拷贝构造函数直接申请一块新的堆区内存,用于存放与拷贝内容相同的数据,这样在析构的时候就不会重复释放同一个堆区的内存了。

深拷贝:在堆区重新申请空间,进行拷贝操作

#include <iostream>
using namespace std;

class Person {
public:
    //无参(默认)构造函数
    Person() {
        cout << "无参构造函数!" << endl;
    }
    //有参构造函数
    Person(int age, int height) {
        cout << "有参构造函数!" << endl;
        m_age = age;
        m_height = new int(height);   //创建在堆区,用指针接收
    }

    // 拷贝构造函数
    Person (const Person& p) {
        cout << "拷贝构造函数!" << endl;
        m_age = p.m_age;
        //默认实现的代码
        //m_age  = p.m_age  这里直接是浅拷贝
        m_height = new int(*p.m_height);
    }

    // 析构函数  将堆区数据进行释放
    ~Person() {
        cout << "析构函数!" << endl;
        // 何时需要自己写析构函数:平时直接使用编译器默认提供的析构函数,但是涉及到堆区数据,则需要自己写析构函数来释放堆区数据
        if (m_height != NULL) { // 这里就会走两个不同堆区的释放
            delete m_height;
            m_height = NULL;    // 防止野指针出现,进行置空操作
        }
    }

public:
    int m_age;
    int* m_height;
};

void test01() {
    Person p1(18, 180);
    Person p2(p1);
    cout << "p1的年龄:" << p1.m_age << ",p1的身高:" << *p1.m_height << endl;
    cout << "p2的年龄:" << p2.m_age << ",p2的身高:" << *p2.m_height << endl;
}
int main() {
    test01();
    return 0;
}

如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。

初始化列表

C++提供了初始化列表语法,用来初始化属性

构造函数():属性1(值1),属性2(值2),()

#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 << "m_A:" << m_A << endl;
        cout << "m_B:" << m_B << endl;
        cout << "m_C:" << m_C << endl;
    }

private:
    int m_A;
    int m_B;
    int m_C;
};
int main() {
    // 初始化列表
    Person p(1,2,3);
    p.printPerson();

    return 0;
}

类对象作为类成员

C++类中的成员可以是另一个类的对象,我们成该成员为对象成员

class A {}
class B {
    A a;
}

那么创建B对象时,A与B的构造和析构顺序是谁先谁后
我猜 构造的时候先B再A 析构 的时候先A再B 错
先构造类中的其他的类的对象,先有零件才能出来整体。
析构翻过来,先析构本类再析构类内的对象

#include <iostream>
using namespace std;

class Phone {
public:
    Phone(string name) {
        m_Phonename = name;
        cout << "Phone构造" << endl;
    }

    ~ Phone() {
        cout << "Phone析构" << endl;
    }
    string m_Phonename;
};

class Person {
public:
    string m_name;
    Phone m_phone;
    //初始化列表可以告诉编译器调用哪一个构造函数 这里phonename是string类型 m_phone(phonename)=== Phone m_phone =  phonename (隐式转换法)
    Person (string name, string phonename): m_name(name), m_phone(phonename) {
        cout << "Person构造" << endl;
    }
    ~ Person() {
        cout << "Person析构" << endl;
    }

    void playGame() {
        cout << m_name << "使用" << m_phone.m_Phonename << "打游戏" << endl;
    }
};

void test01() {
    Person p("jennie", "samsung");
    p.playGame();
}
int main() {
    test01();
    return 0;
}

静态成员

静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员。静态成员分为:

静态成员变量

  • 所有的对象共享同一份数据
  • (同一个类创建出的所有对象都共享,也就是其中一个对象修改了这个属性,其他的对象属性也跟着修改)
  • 在编译阶段分配内存
  • 类内声明类外初始化 必须有初始值

静态成员函数

  • 所有的对象共享一个函数
  • 静态成员函数只能访问静态成员变量

静态成员变量 不属于某个对象,所有的对象都共享同一份数据
因此,静态成员变量有两种访问方式
1.通过对象进行访问
2.通过类名进行访问

#include <iostream>
using namespace std;

// 静态成员变量
class Person {
public:
    static int m_A;

private:
    static int m_B;     // 静态成员变量也是有访问权限的
};
// 静态成员变量初始化
int Person::m_A = 10;
int Person::m_B = 20;

void test01() {
    // 静态成员变量不属于某个对象。所有对象都共享同一份数据,因此有2种访问方式:

    // 1.通过对象
    Person p1;
    p1.m_A = 100;
    cout << "p1.m_A = " << p1.m_A << endl;
    Person p2;
    p2.m_A = 200;
    cout << "p1.m_A = " << p1.m_A << endl;  // 共享同一份数据
    cout << "p2.m_A = " << p2.m_A << endl;

    // 2.通过类名 访问静态成员变量
    cout << "m_A = " << Person::m_A << endl;

}

int main() {
    test01();
    return 0;
}

静态成员函数,静态成员函数不可以访问非静态的成员变量,因为静态函数每个对象都是共用的一个,而非静态变量每个对象都有一个,因此不知道是访问的哪个,因此不能访问。
私有的静态成员函数也无法被类外访问。

#include <iostream>
using namespace std;


class Person {
public:
    static int m_A;
    int m_B;

    static void func() {

        cout << "func调用 " << endl;
        m_A = 100;
        // m_B = 90;   // 错误,静态成员函数不能访问非静态成员变量
    }

private:
    static void func2() {
        cout << "func2调用 " << endl;
       }
};
int Person::m_A = 10;
// int Person::m_B = 100;   // 错误,非静态成员变量不能在类外定义、初始化,也不能通过类名访问

void test01() {
    // 静态成员函数2种调用方式

    // 1.  通过对象访问
    Person p;
    p.func();

    // 2. 通过类名访问
    Person::func();
    // Person::func2();     // 私有静态成员函数不能通过类名访问
}
int main() {
    test01();
    return 0;
}


网站公告

今日签到

点亮在社区的每一天
去签到