3. C++類的成員變量和成員函數(shù)

類的成員變量和普通的變量一樣,從格式上基本沒多區(qū)別。

唯一需要注意是他們的責(zé)任是不同的,成員變量是對對象負(fù)責(zé)的,在類中,使用范圍由類決定,而普通變量則沒有這個說法。

類的成員函數(shù)也和普通函數(shù)一樣,都有返回值和形參。

它與普通函數(shù)的區(qū)別是:成員函數(shù)是一個類的成員,出現(xiàn)在類中,它的作用范圍由類來決定;而普通函數(shù)是獨立的,作用范圍是全局的,或位于某個命名空間內(nèi)。

這里不同變量和不同的函數(shù)我們后面會做一個系統(tǒng)的解釋和分析。

下面還是之前的例程,成員函數(shù)類內(nèi)聲明,類外定義。

ps:早期版本的C++成員變量聲明時不可以對其初始化,后期C++ 11,可以進行成員變量初始化賦值。

#include <iostream>
using namespace std;

//類通常定義在函數(shù)外面
class Person{
public:
    //類包含的變量
    char *name;
    int age;
    //類內(nèi)聲明
    void say();
};

//類外定義
void Person::say(){
    cout << name << "的年齡是" << age << endl;
}

int main(){
    //創(chuàng)建對象
    Person p;
    p.name = "豆豆";
    p.age = 16;
    p.say();
    return 0;
}

void Person::say()

::被稱為作用域運算符或作用域限定符,用來連接類名和函數(shù)名,指明當(dāng)前函數(shù)屬于哪個類,成員函數(shù)在類外定義時必須使用作用域限定符。

注意:在引入了類的類型后,我們再參數(shù)和返回值會有更多的選擇

#include <iostream>
using namespace std;

class Car{
public:
    string name;
    string color;
    int wheel;
public:
    void run();
};

void Car::run()
{
    cout << color << "的" << name << "在跑..." << endl;
}

class CarFatory{
public:
    string name;
    string address;
    string tel;
public:
    Car* repair(Car *c);
};

Car* CarFatory::repair(Car *c)
{
    if(c->wheel < 4){
        c->wheel = 4;
        cout << c->name << "車,修好了" << endl;
    }
    return c;
}

int main(){
    Car *c = new Car();
    c->name = "保時捷";
    c->color = "紅色";
    c->run();//車在跑
    c->wheel = 3;//跑著跑著車輪子掉了了,壞了
    CarFatory *f = new CarFatory();
    Car *newCar = f->repair(c);//修車
    cout << "車有" << newCar->wheel << "個輪子" << endl;
    return 0;
}

在一個類中成員中變量和函數(shù)可以分為多種形態(tài)
我們先看下成員變量的部分
在C中我們經(jīng)常遇到這幾個混亂的變量

局部變量:在一個函數(shù)內(nèi)部定義的變量(包括函數(shù)形參)是局部變量,存儲在棧內(nèi)存,在函數(shù)結(jié)束后自動銷毀。

全局變量:在函數(shù)體外定義的變量,可以為本源文件中其它函數(shù)所公用,有效范圍為從定義變量的位置開始到本源文件結(jié)束,這種類型的變量就稱為“全局變量”。全局變量存儲在靜態(tài)存儲區(qū)域(靜態(tài)內(nèi)存)。

ps:全局變量可以被同一工程項目中其他文件用extern聲明后調(diào)用,對其每次進行修改都會被保存。

靜態(tài)變量又分為:靜態(tài)全局變量和靜態(tài)局部變量

靜態(tài)全局變量:在原先的全局變量前面加上了static進行修飾。存儲在靜態(tài)存儲區(qū)。跟全局變量最大的不同在于,靜態(tài)全局變量不能被其他源文件使用,只能被本源文件使用,對其每次進行修改都會被保存。

靜態(tài)局部變量:在原先的局部變量前面加上了static進行修飾。存儲在靜態(tài)存儲區(qū)內(nèi),等到整個程序結(jié)束才會被銷毀,但是它的作用域依然在函數(shù)體內(nèi)部。

ps:靜態(tài)局部變量一般實際中沒有太大作用,所以這里我們了解下就可以。


需要重點關(guān)注的幾個部分:

1、靜態(tài)成員變量

class Person{
public:
    void show();
public:
     static int height; //靜態(tài)成員變量
private:
    char *name;
    int age;
};

靜態(tài)成員變量屬于類,不屬于某個具體的對象,即使創(chuàng)建多個對象,也只為height分配一份內(nèi)存,所有對象使用的都是這份內(nèi)存中的數(shù)據(jù)。
當(dāng)某個對象修改了height,也會影響到其他對象。

注意:
1、靜態(tài)成員變量必須在類聲明的外部初始化,而且只能在類體外進行。

int Person::height = 0;

初始化時可以賦初值,也可以不賦值。如果不賦值,那么會被默認(rèn)初始化為 0。
全局?jǐn)?shù)據(jù)區(qū)的變量都有默認(rèn)的初始值 0,而動態(tài)數(shù)據(jù)區(qū)(堆區(qū)、棧區(qū))變量的默認(rèn)值是不確定的,一般認(rèn)為是垃圾值。

2、靜態(tài)成員變量的內(nèi)存既不是在聲明類時分配,也不是在創(chuàng)建對象時分配,而是在類外初始化時分配。
3、一個類中可以有一個或多個靜態(tài)成員變量,所有的對象都共享這些靜態(tài)成員變量。
4、靜態(tài)成員變量和普通靜態(tài)變量一樣,都在內(nèi)存分區(qū)中的全局?jǐn)?shù)據(jù)區(qū)分配內(nèi)存,程序結(jié)束時才釋放。
5、靜態(tài)成員變量不隨對象的創(chuàng)建而分配內(nèi)存,也不隨對象的銷毀而釋放內(nèi)存。而普通成員變量在對象創(chuàng)建時分配內(nèi)存,在對象銷毀時釋放內(nèi)存。
6、靜態(tài)成員變量既可以通過對象名訪問,也可以通過類名訪問。

關(guān)于靜態(tài)成員變量的訪問方式:

//通過類類訪問 static 成員變量
Person::height= 180;

//通過對象來訪問 static 成員變量
Person p;
p.height= 20;

//通過對象指針來訪問 static 成員變量
Person *p = new Student();
p->height= 190;

-----

2、靜態(tài)成員函數(shù)

C++中成員函數(shù)也是可以聲明為靜態(tài)成員函數(shù)的,靜態(tài)成員函數(shù)只能訪問靜態(tài)成員。

編譯器在編譯一個普通成員函數(shù)時,會隱式地增加一個形參 this,并把當(dāng)前對象的地址賦值給 this,所以普通成員函數(shù)只能在創(chuàng)建對象后通過對象來調(diào)用,因為它需要當(dāng)前對象的地址。而靜態(tài)成員函數(shù)可以通過類來直接調(diào)用,編譯器不會為它增加形參 this,它不需要當(dāng)前對象的地址,所以不管有沒有創(chuàng)建對象,都可以調(diào)用靜態(tài)成員函數(shù)。

靜態(tài)成員函數(shù)沒有 this 指針,無法在函數(shù)體內(nèi)部訪問某個對象,所以不能調(diào)用普通成員函數(shù),只能調(diào)用靜態(tài)成員函數(shù)。

靜態(tài)成員函數(shù)與普通成員函數(shù)的根本區(qū)別在于:普通成員函數(shù)有 this 指針,可以訪問類中的任意成員;而靜態(tài)成員函數(shù)沒有 this 指針,只能訪問靜態(tài)成員(包括靜態(tài)成員變量和靜態(tài)成員函數(shù))。

include <iostream>

using namespace std;

class Person{
public:
void show();
public: //聲明靜態(tài)成員函數(shù)
static int getAge();
static double getSalary();
private:
static int age;
static double salary;
private:
char *name;
};

int Person::age = 20;
double Person::salary = 5000.0;

void Person::show(){
cout<< name <<"的年齡是"<< age <<",工資是"<< salary <<endl;
}

//定義靜態(tài)成員函數(shù)
int Person::getAge(){
return age;
}

double Person::getSalary(){
return salary;
}

int main(){
int age = Person::getAge();
float salary = Person::getSalary();
cout<<"年齡"<< age <<"的員工工資是"<< salary <<endl;
return 0;
}

-----

3、空類的默認(rèn)成員函數(shù)

關(guān)于C++成員函數(shù)這是我們比較關(guān)注的

讓我們看一下空類中都有什么樣的成員函數(shù),編譯器會為空類提供哪些默認(rèn)成員函數(shù)?分別有什么樣的功能呢?

空類,聲明時編譯器不會生成任何成員函數(shù),對于空類,編譯器不會生成任何的成員函數(shù),只會生成1個字節(jié)的占位符。(在Linux下,是4個字節(jié))

C++空類編譯器自動生成的6個成員函數(shù):
一個缺省的構(gòu)造函數(shù)
一個拷貝構(gòu)造函數(shù)
一個析構(gòu)函數(shù)
一個賦值運算符
兩個取址運算符。

class Empty
{
public:
Empty(); //缺省構(gòu)造函數(shù)
Empty(const Empty &rhs); //拷貝構(gòu)造函數(shù)
~Empty(); //析構(gòu)函數(shù)
Empty& operator=(const Empty &rhs); //賦值運算符
Empty* operator&(); //取址運算符
const Empty* operator&() const; //取址運算符(const版本)
};

使用時的調(diào)用情況:

Empty *e = new Empty(); //缺省構(gòu)造函數(shù)
delete e; //析構(gòu)函數(shù)
Empty e1; //缺省構(gòu)造函數(shù)
Empty e2(e1); //拷貝構(gòu)造函數(shù)
e2 = e1; //賦值運算符
Empty *pe1 = &e1; //取址運算符(非const)
const Empty *pe2 = &e2; //取址運算符(const)

C++編譯器對這些函數(shù)的實現(xiàn):

inline Empty::Empty() //缺省構(gòu)造函數(shù)
{
}

inline Empty::~Empty() //析構(gòu)函數(shù)
{
}

inline Empty *Empty::operator&() //取址運算符(非const)
{
return this;
}

inline const Empty *Empty::operator&() const //取址運算符(const)
{
return this;
}

inline Empty::Empty(const Empty &rhs) //拷貝構(gòu)造函數(shù)
{
//對類的非靜態(tài)數(shù)據(jù)成員進行以"成員為單位"逐一拷貝構(gòu)造
//固定類型的對象拷貝構(gòu)造是從源對象到目標(biāo)對象的"逐位"拷貝
}

inline Empty& Empty::operator=(const Empty &rhs) //賦值運算符
{
//對類的非靜態(tài)數(shù)據(jù)成員進行以"成員為單位"逐一賦值
//固定類型的對象賦值是從源對象到目標(biāo)對象的"逐位"賦值。
}

m是類C中的一個類型為T的非靜態(tài)成員變量,若C沒有聲明拷貝構(gòu)造函數(shù)(賦值運算符), m將會通過T的拷貝構(gòu)造函數(shù)(賦值運算符)被拷貝構(gòu)造(賦值);該規(guī)則遞歸應(yīng)用到m的數(shù)據(jù)成員,直到找到一個拷貝構(gòu)造函數(shù)(賦值運算符)或固定類型(例如:int、double、指針等)為止。

這些函數(shù)我們后續(xù)會依次進行講解

----

4、構(gòu)造函數(shù)

構(gòu)造函數(shù)(Constructor): 它的名字和類名相同,沒有返回值,不需要用戶顯式調(diào)用(用戶也不能調(diào)用),而是在創(chuàng)建對象時自動執(zhí)行。

格式:
聲明:

類名(參數(shù)列表);

類外定義:

類名 :: 類名(參數(shù)列表) : 構(gòu)造函數(shù)的初始化列表{ 函數(shù)體 }

通過構(gòu)造函數(shù)可以在創(chuàng)建對象的同時,對對象的成員變量(屬性)進行初始化,這樣就簡化了創(chuàng)建對象后再賦值屬性值的過程。

include <iostream>

using namespace std;

class Student{
private:
char *name;
int age;
float score;
public:
//聲明構(gòu)造函數(shù)
Student(char *name, int age, float score);
//聲明普通成員函數(shù)
void show();
};

//定義構(gòu)造函數(shù)
Student::Student(char *name, int age, float score){
this->name = name;
this->age = age;
this->score = score;
}

//定義普通成員函數(shù)
void Student::show(){
cout<<name<<"的年齡是"<<age<<",成績是"<<score<<endl;
}

int main(){
//創(chuàng)建對象時向構(gòu)造函數(shù)傳參
Student stu("豆豆", 20, 93.0);
stu.show();
//創(chuàng)建對象時向構(gòu)造函數(shù)傳參
Student *pstu = new Student("哈哈", 21, 96.0);
pstu->show();
return 0;
}

![image.png](https://upload-images.jianshu.io/upload_images/16823531-5062de1424bbdadd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

構(gòu)造函數(shù)的一項重要功能是對成員變量進行初始化,為了達到這個目的,可以在構(gòu)造函數(shù)的函數(shù)體中對成員變量一一賦值,還可以采用初始化列表,從而使代碼更加簡潔。

include <iostream>

using namespace std;

class Student{
private:
char *name;
int age;
float score;
public:
Student(char *name, int age, float score);
void show();
};

//采用初始化列表
Student::Student(char *name, int age, float score): name(name), age(age), score(score){
//TODO:
}

void Student::show(){
cout<<name<<"的年齡是"<<age<<",成績是"<<score<<endl;
}

int main(){
Student stu("豆豆", 20, 93.0);
stu.show();
Student *pstu = new Student("哈哈", 21, 96.0);
pstu->show();
return 0;
}

注意:
1、構(gòu)造函數(shù)必須是 public 屬性的,否則創(chuàng)建對象時無法調(diào)用。
2、構(gòu)造函數(shù)沒有返回值。
3、函數(shù)體中不能有 return 語句。
4、使用構(gòu)造函數(shù)初始化列表并沒有效率上的優(yōu)勢,僅僅是書寫方便。
5、初始化列表可以用于全部成員變量,也可以只用于部分成員變量。
6、成員變量的初始化順序與初始化列表中列出的變量的順序無關(guān),它只與成員變量在類中聲明的順序有關(guān)。

#include <iostream>
using namespace std;

class Demo{
private:
    int a;
    int b;
public:
    Demo(int b1);
    void show();
};

Demo::Demo(int b1): b(b1), a(b){ }
void Demo::show(){ cout<< a <<", "<< b <<endl; }

int main(){
    Demo obj(100);
    obj.show();
    return 0;
}
image.png

上面程序初始化列表等價于

Demo::Demo(int b1): m_b(b1), m_a(b){
    a = b;
    b = b1;
}

給 a 賦值時,b 還未被初始化,它的值是不確定的,所以輸出的 a 的值是一個奇怪的數(shù)字;
obj 在棧上分配內(nèi)存,成員變量的初始值是不確定的。
好像感覺初始化列表除了簡潔沒有其他作用,實則不然,初始化 const 成員變量的唯一方法就是使用初始化列表。

class Array{
private:
    const int len;
    int *arr;
public:
    Array(int len);
};

//必須使用初始化列表來初始化 len
Array::Array(int len): len(len){
    arr = new int[len];
}

默認(rèn)構(gòu)造函數(shù)

如果用戶自己沒有定義構(gòu)造函數(shù),那么編譯器會自動生成一個默認(rèn)的構(gòu)造函數(shù),只是這個構(gòu)造函數(shù)的函數(shù)體是空的,也沒有形參,也不執(zhí)行任何操作。

Student(){}

一個類必須有構(gòu)造函數(shù),要么用戶自己定義,要么編譯器自動生成。
一旦用戶自己定義了構(gòu)造函數(shù),不管有幾個,也不管形參如何,編譯器都不再自動生成。
注意:最后需要注意的一點是,調(diào)用沒有參數(shù)的構(gòu)造函數(shù)也可以省略括號。
在棧上創(chuàng)建對象可以寫作Student stu()或Student stu
在堆上創(chuàng)建對象可以寫作Student *pstu = new Student()或Student *pstu = new Student
它們一樣都會調(diào)用構(gòu)造函數(shù) Student()。


5、構(gòu)造函數(shù)的重載

說到構(gòu)造函數(shù)重載,我們就需要說一下重載的概念了。

函數(shù)重載是一種特殊情況,C++允許在同一作用域中聲明幾個類似的同名函數(shù),這些同名函數(shù)的形參列表(參數(shù)個數(shù),類型,順序)必須不同,

常用來處理實現(xiàn)功能類似數(shù)據(jù)類型不同的問題。

//全局的函數(shù)重載
int get();
int get(int a);
int get(float a);
int get(int a, int b);

class Calculate{
private:
    int a;
    int b;
public:
    //構(gòu)造函數(shù)重載
    Calculate();
    Calculate(int a);
    Calculate(int a, int b);

    //成員函數(shù)重載
    void sum();
    //int sum();   //不是重載,與返回值無關(guān)
    void sum(int a, int b);
    void sum(int a, int b, int c);
    void sum(double a, double b);
};

void Calculate::sum(){}
//int Calculate::sum(){}
void Calculate::sum(int a, int b){}
void Calculate::sum(int a, int b, int c){}
void Calculate::sum(double a, double b){}

后續(xù)我們還會繼續(xù)討論重載。


6、析構(gòu)函數(shù)

析構(gòu)函數(shù)也是一種特殊的成員函數(shù),沒有返回值,不需要程序員顯式調(diào)用,而是在銷毀對象時自動執(zhí)行。
構(gòu)造函數(shù)的名字和類名相同,而析構(gòu)函數(shù)的名字是在類名前面加一個~符號。

注意:
1、析構(gòu)函數(shù)沒有參數(shù),不能被重載
2、一個類只能有一個析構(gòu)函數(shù)。
3、如果用戶沒有定義析構(gòu)函數(shù),編譯器會自動生成一個默認(rèn)的析構(gòu)函數(shù)。

#include <iostream>
using namespace std;

/*
封裝一個數(shù)組類來看delete的作用
*/
class Array{
public:
    Array(int len); //構(gòu)造函數(shù)
    ~Array(); //析構(gòu)函數(shù)
public:
    void input(); //輸入數(shù)組元素函數(shù)
    void out(); //顯示數(shù)組元素函數(shù)
private:
    int* getElement(int i); //獲取第i個元素的指針
private:
    const int len; //數(shù)組的長度
    int *arr; //數(shù)組指針
    int *p; //指向數(shù)組元素的指針
};

Array::Array(int len): len(len){ //使用初始化列表來給len賦值
    if(len > 0){
        arr = new int[len]; //動態(tài)內(nèi)存申請一個塊用于數(shù)組的內(nèi)存
    }
    else{
        arr = NULL;
    }
}

Array::~Array(){
    delete[] arr; //釋放內(nèi)存
}
void Array::input(){
    for(int i = 0; p = getElement(i); i++){
        cin>>*getElement(i);
    }
}

void Array::out(){
    for(int i = 0; p = getElement(i); i++){
        if(i == len - 1){
            cout<<*getElement(i)<<endl;
        }
        else{
            cout<<*getElement(i)<<", ";
        }
    }
}

int * Array::getElement(int i){
    if(!arr || i < 0 || i >= len){
        return NULL;
    }
    else{
        return arr + i;
    }
}

int main(){
    int n;
    cout<<"輸入數(shù)組的長度: ";
    cin>>n;
    Array *parr = new Array(n); //創(chuàng)建一個有n個元素的數(shù)組對象
    //輸入數(shù)組元素
    cout<<"請輸入 "<<n<<" 個元素: ";
    parr->input();
    //輸出數(shù)組元素
    cout<<"數(shù)組內(nèi)元素是: ";
    parr->out();
    //刪除數(shù)組(對象)
    delete parr;
    return 0;
}
image.png

注意:
1、new 分配內(nèi)存時會調(diào)用構(gòu)造函數(shù)。
2、delete 釋放內(nèi)存時會調(diào)用析構(gòu)函數(shù)。
3、構(gòu)造函數(shù)和析構(gòu)函數(shù)對于類來說是不可或缺的。

析構(gòu)函數(shù)的調(diào)用時機
析構(gòu)函數(shù)在對象被銷毀時調(diào)用,而對象的銷毀時機與它所在的內(nèi)存區(qū)域有關(guān)。
在所有函數(shù)之外創(chuàng)建的對象是全局對象,它和全局變量類似,位于內(nèi)存分區(qū)中的全局?jǐn)?shù)據(jù)區(qū),程序在結(jié)束執(zhí)行時會調(diào)用這些對象的析構(gòu)函數(shù)。
在函數(shù)內(nèi)部創(chuàng)建的對象是局部對象,它和局部變量類似,位于棧區(qū),函數(shù)執(zhí)行結(jié)束時會調(diào)用這些對象的析構(gòu)函數(shù)。
new 創(chuàng)建的對象位于堆區(qū),通過 delete 刪除時才會調(diào)用析構(gòu)函數(shù);如果沒有 delete,析構(gòu)函數(shù)就不會被執(zhí)行。

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

class Test{
public:
    Test(string s);
    ~Test();
private:
    string s;
};

Test::Test(string s): s(s){ cout<<this->s<<"構(gòu)造函數(shù)調(diào)用"<<endl; }
Test::~Test(){ cout<<s<<"析構(gòu)函數(shù)調(diào)用"<<endl; }

void function(){
    //局部對象
    Test obj1("對象1");
}

//全局對象
Test obj2("對象2");

int main(){
    function();
    //局部對象
    Test obj3("對象3");
    //new創(chuàng)建的對象
    Test *pobj4 = new Test("對象4");
    return 0;
}
image.png

7、拷貝構(gòu)造函數(shù)(復(fù)制構(gòu)造函數(shù))

拷貝構(gòu)造函數(shù)是一種特殊的構(gòu)造函數(shù),具有單個形參,該形參(常用const修飾)是對該類類型的引用。
當(dāng)定義一個新對象并用一個同類型的對象對它進行初始化時,將顯示使用復(fù)制構(gòu)造函數(shù)。
當(dāng)該類型的對象傳遞給函數(shù)或從函數(shù)返回該類型的對象時,將隱式調(diào)用復(fù)制構(gòu)造函數(shù)。

C++支持兩種初始化形式:

復(fù)制初始化   int a = 5;
直接初始化   int a(5);

對于其他類型沒有什么區(qū)別,對于類類型直接初始化直接調(diào)用實參匹配的構(gòu)造函數(shù),復(fù)制初始化總是調(diào)用復(fù)制構(gòu)造函數(shù),也就是說:

A x(2);  //直接初始化,調(diào)用構(gòu)造函數(shù)
A y = x;  //復(fù)制初始化,調(diào)用復(fù)制構(gòu)造函數(shù)

必須定義復(fù)制構(gòu)造函數(shù)的情況:
只包含類類型成員或內(nèi)置類型(但不是指針類型)成員的類,無須顯式地定義復(fù)制構(gòu)造函數(shù)也可以復(fù)制;
有的類有一個數(shù)據(jù)成員是指針,或者是有成員表示在構(gòu)造函數(shù)中分配的其他資源,這兩種情況下都必須定義復(fù)制構(gòu)造函數(shù)。

什么情況使用復(fù)制構(gòu)造函數(shù):
類的對象需要拷貝時,拷貝構(gòu)造函數(shù)將會被調(diào)用。以下情況都會調(diào)用拷貝構(gòu)造函數(shù):
(1)一個對象以值傳遞的方式傳入函數(shù)體
(2)一個對象以值傳遞的方式從函數(shù)返回
(3)一個對象需要通過另外一個對象進行初始化。

深拷貝和淺拷貝:
淺拷貝,指的是在對象復(fù)制時,只對對象中的數(shù)據(jù)成員進行簡單的賦值,默認(rèn)拷貝構(gòu)造函數(shù)執(zhí)行的也是淺拷貝。

在“深拷貝”的情況下,對于對象中動態(tài)成員,就不能僅僅簡單地賦值了,而應(yīng)該重新動態(tài)分配空間
深拷貝:如果一個類擁有資源,當(dāng)這個類的對象發(fā)生復(fù)制過程的時候,資源重新分配,這個過程就是重新動態(tài)分配空間

如果沒有自定義拷貝構(gòu)造函數(shù),則系統(tǒng)會創(chuàng)建默認(rèn)的拷貝構(gòu)造函數(shù),但系統(tǒng)創(chuàng)建的默認(rèn)復(fù)制構(gòu)造函數(shù)只會執(zhí)行“淺拷貝”,即將被拷貝對象的數(shù)據(jù)成員的值一一賦值給新創(chuàng)建的對象;

若該類的數(shù)據(jù)成員中有指針成員,則會使得新的對象的指針?biāo)赶虻牡刂放c被拷貝對象的指針?biāo)赶虻牡刂废嗤琩elete該指針時則會導(dǎo)致兩次重復(fù)delete而出錯。

下面是示例:

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

class Person
{
public :
    // 構(gòu)造函數(shù)
    Person(char * pN);
    // 系統(tǒng)創(chuàng)建的默認(rèn)復(fù)制構(gòu)造函數(shù),只做位模式拷貝
    Person(Person & p);
    ~Person();
private :
    char* pName;
};

Person::Person(char * pN){
    cout << "構(gòu)造函數(shù)被調(diào)用"<<endl;
    pName = new char[strlen(pN) + 1];
    //在堆中開辟一個內(nèi)存塊存放pN所指的字符串
    if(pName != NULL)
    {
        //如果pName不是空指針,則把形參指針pN所指的字符串復(fù)制給它
        strcpy(pName ,pN);
    }
}

Person::Person(Person &p){
    //使兩個字符串指針指向同一地址位置
    pName = p.pName;
}

Person::~Person(){
    delete pName;
    cout << "析構(gòu)函數(shù)被調(diào)用"<<endl;
}

int main( )
{
    /*p1和p2的指針都指向了同一個地址
    函數(shù)結(jié)束析構(gòu)時
    同一個地址被delete兩次
    */

    Person p1("豆豆");
    Person p2(p1);

    return 0;
}
image.png
// 下面自己設(shè)計復(fù)制構(gòu)造函數(shù),實現(xiàn)“深拷貝”,即不讓指針指向同一地址,而是重新申請一塊內(nèi)存給新的對象的指針數(shù)據(jù)成員
Person::Person(Person & p)
{
    // 用運算符new為新對象的指針數(shù)據(jù)成員分配空間
    pName = new char[strlen(p.pName)+ 1];
    if(pName)
    {
    // 復(fù)制內(nèi)容
    strcpy(pName ,p.pName);
    }
    // 則新創(chuàng)建的對象的pName與原對象chs的pName不再指向同一地址了
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容