TOPIC NOTES

C/C++基础之二(类的构造函数)

C++面向对象
文章目录
  1. C/C++基础之二(类的构造函数)
  2. Talk is cheap. Show me the code.
  3. 1. 构造函数,有参,无参?
  4. 2. 析构函数的调用时机?
  5. 3. 如何在构造函数中初始化某个成员类?
  6. 4. 拷贝构造函数的凶残?
  7. 5. 操作符重载的奇妙设计!
  8. 我们写了一些属性以及get/set方法,当这个类交给一个工具类的时候他想做一些操作,因为属性一般是private的,因此他必须要调用get,set方法,那么就出现了一种叫做firend的函数,可以直接访问私有属性,上代码:
  9. 然后上面的程序似乎没有问题,但是其实是有问题的。这个问题来自于类的拷贝:
  10. 深拷贝(deep copy):在拷贝构造和赋值运算符中,重新分配内存并复制内容
  11. 下面看看我们的例子,使用深拷贝构造函数&‘=’运算符重写,解决这个棘手的问题:
  12. 输出结果

C/C++基础之二(类的构造函数)

Talk is cheap. Show me the code.

1. 构造函数,有参,无参?

2. 析构函数的调用时机?

3. 如何在构造函数中初始化某个成员类?

4. 拷贝构造函数的凶残?

5. 操作符重载的奇妙设计!

cpp
#include <iostream>
#include <string.h>

using namespace std;

class Person
{
private:
    char *name;
    char *work;
    char *sex;
    int age;
public:
    Person();
    Person(char *name,char *work,int age,char *sex=(char *)"f");
    ~Person();
    void setAge(int age);
    void setName(char *name);
    void printInfo();
};


//无参构造函数
Person::Person(/* args */)
{
    cout << "Person()" << endl;
    // 对指针变量赋初值很重要,否则是野指针,直接访问非常危险,会有问题的
    this->name = NULL;
    this->work = NULL;
    this->sex = NULL;
}

Person::Person(char *name,char *work,int age,char *sex)
{   
    // 下面变量共享内存的写法,也叫做浅拷贝,如果外部被释放,那么这里就成了野指针。很危险
    // this->name = name;
    // this->work = work;
    // 分配内存,也叫做深拷贝,安全,但要记得自己释放new出来的内存,释放交给析构函数
    this->name = new char[strlen(name)+1];
    strcpy(this->name,name);

    this->work = new char[strlen(work)+1];
    strcpy(this->work,work);
    
    this->sex = new char[strlen(sex)+1];
    strcpy(this->sex,sex);

    this->age = age;
    cout << "Person(char *name,char *work,int age)" << ",name:"<< name << ",work:"<< work <<",age:"<< age <<",sex:"<< sex << endl;
}

void Person::printInfo()
{
    cout << "name:"<< name << ",work:"<< work <<",age:"<< age << endl;
}

// 析构函数
Person::~Person()
{
    cout << "~Person(),name:" << this->name << endl;
    if (this->name)
    {   
        cout << "delete name:" << this->name << endl;
       // C++ 要严格遵守 new ↔ delete,new[] ↔ delete[]。
       // delete this->name;
        delete[] this->name;
    }
    if (this->work)
    {   
        cout << "delete work:" << this->work << endl;
        // C++ 要严格遵守 new ↔ delete,new[] ↔ delete[]。
        delete[] this->work;
    }

    if(this->sex)
    {
        // C++ 要严格遵守 new ↔ delete,new[] ↔ delete[]。
        cout << "delete sex:" << this->sex << endl;
        delete[] this->sex;
    }

}

class Student
{
private:
    Person father;
    Person mother;
    int id;
public:
    Student()
    {
        cout << "Student()" << endl;
    }
    // 构造函数中完成成员变量的初始化,成员变量的初始化和:father(...),mother(...)无关,之和上面的成员定义顺序有关
    Student(char *fatherName,char *motherName,int id):father(fatherName,"CFO",28,(char *)"M"),mother(motherName,"BOSS",18)
    {
        this->id = id;
    }
    ~Student()
    {
        cout << "~Student()"<<endl;
    }
};

void person_test()
{
    Person *p = new Person("lisi","student",18);
    //new出来的内存一定要释放掉,否则它是不会释放的
    delete p;
}

int main()
{
    Person per;
    //注意下面这行代码并没有创建per1对象,只是一个方法声明,类似 int sum(); 
    Person per1();
    // 调用有参数的构造函数,最后一个sex参数使用默认值
    Person per2("zhangsan","laywer",48);
    Person per3("wangwu","CEO",29,"M");
    person_test();
    Student s("Jack","Alice",10);
    return 0;
}
  • 输出如下
text
Person()
Person(char *name,char *work,int age),name:zhangsan,work:laywer,age:48,sex:f
Person(char *name,char *work,int age),name:wangwu,work:CEO,age:29,sex:M
Person(char *name,char *work,int age),name:lisi,work:student,age:18,sex:f
~Person(),name:lisi
delete name:lisi
delete work:student
delete sex:f
Person(char *name,char *work,int age),name:Jack,work:CFO,age:28,sex:M
Person(char *name,char *work,int age),name:Alice,work:BOSS,age:18,sex:f
~Student()
~Person(),name:Alice
delete name:Alice
delete work:BOSS
delete sex:f
~Person(),name:Jack
delete name:Jack
delete work:CFO
delete sex:M
~Person(),name:wangwu
delete name:wangwu
delete work:CEO
delete sex:M
~Person(),name:zhangsan
delete name:zhangsan
delete work:laywer
delete sex:f
~Person(),name:

我们写了一些属性以及get/set方法,当这个类交给一个工具类的时候他想做一些操作,因为属性一般是private的,因此他必须要调用get,set方法,那么就出现了一种叫做firend的函数,可以直接访问私有属性,上代码:
cpp
#include <iostream>

using namespace std;

class Point;

class PointUtils
{
private:
    /* data */
public:
    PointUtils
(/* args */);
    Point pointSum(const Point& p1,const Point& p2);
    ~PointUtils
();
};

class Point{
private:
    int x;
    int y;

public:
    Point(){}

    Point(int x,int y){
        this->x = x;
        this->y = y;
    }

    void setX(int x){
        this->x = x;
    }

    void setY(int y){
        this->y = y;
    }

    int getX(){
        return this->x;
    }

    int getY(){
        return this->y;
    }
    //声明PointUtils的pointSum是我的朋友,可以访问我的属性
    friend Point PointUtils::pointSum(const Point& p1,const Point& p2);

    void printInfo(){
        cout << "x:" << this->x << ",y:" << this->y << endl;
    }

};

PointUtils::PointUtils(/* args */)
{
}

Point PointUtils::pointSum(const Point& p1,const Point& p2){
    Point result;
    //友元函数,可以直接访问x,y,就不用使用get方法啦
    result.x = p1.x + p2.x;
    result.y = p1.y + p2.y;
    return result;
}

PointUtils::~PointUtils()
{
}

int main(){
    Point p1(10,20);
    Point p2(20,30);
    PointUtils util;
    Point result = util.pointSum(p1,p2);
    result.printInfo();
    return 0;
}

然后上面的程序似乎没有问题,但是其实是有问题的。这个问题来自于类的拷贝:

1️⃣ 浅拷贝的定义

浅拷贝就是 直接拷贝对象的所有成员,包括指针 只拷贝指针本身的值(地址),而不拷贝指针指向的内容。

例如:

class Demo {
public:
int* ptr;
Demo(int val) { ptr = new int(val); }
~Demo() { delete ptr; }
};

int main() {
Demo d1(5);
Demo d2 = d1; // 默认拷贝构造函数执行 → 浅拷贝
}

d2.ptr 和 d1.ptr 指向同一块堆内存

当 d1 析构时释放了内存 → d2.ptr 悬空

再析构 d2 → double free / 崩溃

✅ 这就是浅拷贝的典型问题。

2️⃣ 什么时候会触发浅拷贝
(1) 按值传递对象
void foo(Demo d) { … } // d 是按值传递
Demo obj(5);
foo(obj); // 触发拷贝构造(默认浅拷贝)

函数内部会调用 拷贝构造函数

默认拷贝构造函数就是浅拷贝

(2) 返回对象(按值返回)
Demo createDemo() {
Demo d(10);
return d; // 触发拷贝构造(默认浅拷贝)
}

编译器通常会做 返回值优化(RVO) 避免拷贝,但如果没有 RVO,也会发生浅拷贝

(3) 对象赋值
Demo d1(5);
Demo d2(10);
d2 = d1; // 默认赋值运算符 → 浅拷贝

这里 d2 的原 ptr 被覆盖

如果原 ptr 已经分配了内存,可能造成内存泄漏

同时 d1 和 d2 共享同一块内存 → double-free

3️⃣ 浅拷贝的问题

悬空指针:多个对象指向同一块动态分配内存

double-free:析构函数释放同一块内存两次

内存泄漏:赋值时忘记释放旧的资源

4️⃣ 解决方法

深拷贝(deep copy):在拷贝构造和赋值运算符中,重新分配内存并复制内容

使用智能指针或 std::string:自动管理内存,避免浅拷贝问题

下面看看我们的例子,使用深拷贝构造函数&‘=’运算符重写,解决这个棘手的问题:
cpp
#include <iostream>
#include <string.h>

using namespace std;

class Point;

class PointUtils
{
private:
    /* data */
public:
    PointUtils
(/* args */);
    Point pointSum(const Point& p1,const Point& p2);
    ~PointUtils
();
};

class Point{
private:
    int x;
    int y;
    char *niceName;

public:
    Point(){
        niceName = NULL;
        cout << "Point()" << endl;
    }

    // 深拷贝构造函数
    Point(const Point& p){
        cout << "Copy (const Point& p)" << endl;
        this->x = p.x;
        this->y = p.y;
        if(p.niceName){
            this->niceName = new char[strlen(p.niceName)+1];
            strcpy(this->niceName,p.niceName);
        }else{
            this->niceName = nullptr;
        }
    }

    // 赋值运算符
    Point& operator=(const Point& other){
        cout << "operator=" << endl;
        if(this == &other) return *this;
        x = other.x;
        y = other.y;
        delete[] niceName;
        if(other.niceName){
            niceName = new char[strlen(other.niceName)+1];
            strcpy(niceName, other.niceName);
        } else niceName = nullptr;
        return *this;
    }

    Point(int x,int y,const char *niceName){
        cout << "Point(int x,int y)" << "x:" << x <<",y:" << y << niceName << endl;
        this->x = x;
        this->y = y;
        
        this->niceName = new char[strlen(niceName)+1];
        strcpy(this->niceName,niceName);
    }

    void setX(int x){
        this->x = x;
    }

    void setY(int y){
        this->y = y;
    }

    int getX(){
        return this->x;
    }

    int getY(){
        return this->y;
    }

    friend Point PointUtils::pointSum(const Point& p1,const Point& p2);

    // 重载  int + point
    friend Point operator+(int a, Point& p){
        cout << "operator+(int a, Point& p)" << endl;
        Point result;
        result.x = a + p.x;
        result.y = a + p.y;
        result.niceName = new char[10];
        strcpy(result.niceName,"operator+");
        return result;
    }

    //重载 point + int
    friend Point operator+(Point& p, int a){
        cout << "operator+(Point& p, int a)" << endl;
        Point result;
        result.x = a + p.x;
        result.y = a + p.y;
        result.niceName = new char[10];
        strcpy(result.niceName,"operator+");
        // result.niceName = nullptr;
        return result;
    }

    // 重载 前++
    Point& operator++(){
        cout << "operator++()" << endl;
        this->x =  this->x + 1;
        this->y = this->y + 1;
        if (niceName)
        {
           delete[] niceName;
        }
        niceName = new char[10+1];
        strcpy(niceName,"operator++");
        return *this;
    }

    Point operator++(int){
        Point temp(*this); // 保存当前值
        x++; y++;
        return temp;       // 返回修改前的副本
    }


    void printInfo(){
        cout << "printInfo" << "x:" << this->x << ",y:" << this->y;
        if (niceName)
        {
           cout << niceName;
        }
        cout << endl;
    }

    ~Point(){
        cout << "~Point()" << "x:" << this->x << ",y:" << this->y;
        if (niceName)
        {
            cout << "delete:" << niceName;
            // delete只能删除 new 出来的,niceName = "abcd" 就不能删除哦!否则要出大问题
            delete[] niceName;
        }
        cout << endl;
    //    cout << this << end;
    }

};

PointUtils::PointUtils(/* args */)
{
}

Point PointUtils::pointSum(const Point& p1,const Point& p2){
    Point result;
    result.x = p1.x + p2.x;
    result.y = p1.y + p2.y;

    // 判断 p1.niceName 和 p2.niceName 是否为空
    const char* name1 = p1.niceName ? p1.niceName : "";
    const char* name2 = p2.niceName ? p2.niceName : "";

    // 分配内存:长度 = name1 + name2 + 1('\0')
    size_t len = strlen(name1) + strlen(name2) + 1;
    result.niceName = new char[len];

    // 拼接字符串
    strcpy(result.niceName, name1);
    strcat(result.niceName, name2);

    return result;
}

PointUtils::~PointUtils()
{
}

int main(){
    // Point p1(10,20);
    // Point p2(20,30);
    // PointUtils util;
    // Point result = util.pointSum(p1,p2);
    // Point result = p1.add(p2);
    int ax[] = {1,2};
    int ay[] = {1,2};
    int bx[] = {3,4};
    int by[] = {3,4};
    PointUtils util;
    Point pArray[2];

    for(int i=0;i<2;i++){
        cout << "For" << endl;
        char* aNiceName = (char*)malloc(sizeof(char)*2+1);
        sprintf(aNiceName, "a%d", i);
        char* bNiceName = (char*)malloc(sizeof(char)*2+1);
        sprintf(bNiceName, "b%d", i);
        Point p1(ax[i],ay[i],aNiceName);
        Point p2(bx[i],by[i],bNiceName);
        Point result = util.pointSum(p1,p2);
        result.printInfo();
        // operator= 对于‘=’的重载生效,深拷贝!
        pArray[i] = result;
        free(aNiceName);
        free(bNiceName);
    }
    cout << "***last dance***" << endl;

    Point result2 = 3 + pArray[0];
    Point result3 =  pArray[1] + 3;
    result2.printInfo();
    result3.printInfo();
    // 运算符重载,前++
    ++result3;
    result3.printInfo();

    result2++;
    result2.printInfo();
    return 0;
}

输出结果
cpp
Point()
Point()
For
Point(int x,int y)x:1,y:1a0
Point(int x,int y)x:3,y:3b0
Point()
printInfox:4,y:4a0b0
operator=
~Point()x:4,y:4delete:a0b0
~Point()x:3,y:3delete:b0
~Point()x:1,y:1delete:a0
For
Point(int x,int y)x:2,y:2a1
Point(int x,int y)x:4,y:4b1
Point()
printInfox:6,y:6a1b1
operator=
~Point()x:6,y:6delete:a1b1
~Point()x:4,y:4delete:b1
~Point()x:2,y:2delete:a1
***last dance***
operator+(int a, Point& p)
Point()
operator+(Point& p, int a)
Point()
printInfox:7,y:7operator+
printInfox:9,y:9operator+
operator++()
printInfox:10,y:10operator++
Copy (const Point& p)
not null?operator+~Point()x:7,y:7delete:operator+
printInfox:8,y:8operator+
~Point()x:10,y:10delete:operator++
~Point()x:8,y:8delete:operator+
~Point()x:6,y:6delete:a1b1
~Point()x:4,y:4delete:a0b0

每一个不曾起舞的日子,都是对生命的一种辜负。

继续阅读C++