012-對象類型的參數和返回值、友元函數、內部類和局部類

《C++文章匯總》
上一篇介紹了《011-const成員、拷貝構造函數、淺拷貝和深拷貝》,本文介紹對象類型的參數和返回值、友元函數、內部類和局部類。

1.函數的參數若有對象,一般寫成引用類型或指針,否則會調用拷貝構造函數重新生成一個對象,相當于Car car = car1會調用拷貝構造函數生成新的對象

? 使用對象類型作為函數的參數或者返回值,可能會產生一些不必要的中間對象

class Car{
public:
    int m_price;
    Car(){
        cout << "Car() - " << this << endl;
    }
    Car(const Car &car){
        cout << "Car(const Car &) - " << this << endl;
    }
    void run(){
        cout << "run()" << endl;
    }
};
void test1(Car &car){
    
}
int main(){
    
    Car car1;
    test1(car1);
    
    getchar();
    return 0;
}

? 返回值類型為對象類型,Windows下會調用拷貝構造函數,Mac下不會調用拷貝構造函數,Mac下編譯器做了優化

class Car{
public:
    int m_price;
    Car(){
        cout << "Car() - " << this << endl;
    }
    Car(const Car &car){
        cout << "Car(const Car &) - " << this << endl;
    }
    void run(){
        cout << "run()" << endl;
    }
};
Car test2(){
    Car car;
    return car;
}
int main(){  
    Car car2;
    car2 = test2();
        return 0;
}
//輸出
Car() - 0x7ffeefbff378
Car() - 0x7ffeefbff370

windows下會在main函數棧空間中預留一塊內存地址,調用main函數時,將此內存地址傳入到test2函數中,在test2函數中調用拷貝構造函數生成car對象,將car對象放入傳入的main函數的內存地址中,此時main函數中就可以使用此對象了,故會調用三次構造函數

Car() - 0x7ffeefbff378
Car() - 0x7ffeefbff370
Car(const Car &) - 0x7ffeefbff89B

若在main函數中新建Car對象時直接賦值,編譯器會做優化,windows下會調用兩次構造函數并不會調用三次構造函數,Mac下只會調用一次構造函數Car() - 0x7ffeefbff378

Car test2(){
    Car car;
    return car;
}
int main(){
      Car car3 = test2();
}
//輸出
Car() - 0x7ffeefbff378
Car(const Car &) - 0x7ffeefbff89B

故一般不推薦返回對象類型,因為會生成一些不必要的中間對象

2.匿名對象(臨時對象)

? 匿名對象:沒有變量名、沒有被指針指向的對象,用完后馬上調用析構

class Car{
public:
    int m_price;
    Car(){
        cout << "Car() - " << this << endl;
    }
    ~Car(){
        cout << "~Car() - " << this << endl;
    }
    Car(const Car &car){
        cout << "Car(const Car &) - " << this << endl;
    }
    void run(){
        cout << "run()" << endl;
    }
};
int main(){
    
    cout << 1 << endl;
    Car().run();
    cout << 2 << endl;
    
    getchar();
    return 0;
}
//輸出
1
Car() - 0x7ffeefbff3f8
run()
~Car() - 0x7ffeefbff3f8
2

匿名對象作為參數,直接賦值給參數,不會調用拷貝構造函數新建對象,編譯器識別到是匿名對象后直接賦值給參數

class Car{
public:
    int m_price;
    Car(){
        cout << "Car() - " << this << endl;
    }
    ~Car(){
        cout << "~Car() - " << this << endl;
    }
    Car(const Car &car){
        cout << "Car(const Car &) - " << this << endl;
    }
    void run(){
        cout << "run()" << endl;
    }
};
void test(Car car){
    
}
int main(){
      test(Car());
}
//輸出
Car() - 0x7ffeefbff3f8
~Car() - 0x7ffeefbff3f8

返回值為對象且是匿名對象,編譯器優化,不會調用拷貝構造函數,直接將匿名對象賦值給main函數中的對象

Car test2(){
    return Car();
}
int main(){
    Car car;
    car = test2();
    car.run();
}
//輸出
Car() - 0x7ffeefbff3f8
Car() - 0x7ffeefbff3f0
~Car() - 0x7ffeefbff3f0
run()0x7ffeefbff3f8

3.隱式構造(轉換構造)

? C++中存在隱式構造的現象:某些情況下,會隱式調用單參數的構造函數

class Person{
    int m_age;
public:
    Person(){
        cout << "Person() - " << this << endl;
    }
    Person(int age):m_age(age){
        cout << "Person(int) - " << this << endl;
    }
    Person(const Person &person){
        cout << "Person(const Person &person) - " << this << endl;
    }
    ~Person(){
        cout << "~Person() - " << this << endl;
    }
    void display(){
        cout << "display() - age is " << this->m_age << endl;
    }
};
int main(){
    Person p1 = 20;
    getchar();
    return 0;
}
//輸出
Person(int) - 0x7ffeefbff3f8

直接將int類型值賦值給對象,會調用單參數為int類型的構造函數,Person p1 = 20 <=> Person p1(20);

同理,參數類型為對象,直接將int類型傳入,會調用單參數構造函數

void test(Person person){
    
}
int main(){
    test(30);
}
//輸出
Person(int) - 0x7ffeefbff3f8
~Person() - 0x7ffeefbff3f8

同理,函數的返回值類型為對象,將int類型返回,會隱式調用單參數構造函數

Person test1(){
    return 40;
}

int main(){
    test1();
}
//輸出
Person(int) - 0x7ffeefbff3f8
~Person() - 0x7ffeefbff3f8

int類型值賦值給person對象,會調用隱式構造函數。p1=40<=>p1 = Person(40)

int main(){
    Person p1;
    p1 = 40;
}
//輸出
Person() - 0x7ffeefbff3f8
Person(int) - 0x7ffeefbff3f0
~Person() - 0x7ffeefbff3f0

? 可以通過關鍵字explicit禁止掉隱式構造


圖片.png

? 若沒有單參數構造函數,則隱式調用不存在,若雙參數構造函數中有一個有默認賦值,則可進行隱式構造調用

class Person{
    int m_age;
    int m_height;
public:
    Person(){
        cout << "Person() - " << this << endl;
    }
    Person(int age,int height=0):m_age(age){
        cout << "Person(int) - " << this << endl;
    }
    Person(const Person &person){
        cout << "Person(const Person &person) - " << this << endl;
    }
    ~Person(){
        cout << "~Person() - " << this << endl;
    }
    void display(){
        cout << "display() - age is " << this->m_age << endl;
    }
};
void test(Person person){
    
}
Person test1(){
    return 40;
}

int main(){
    Person p1;
    p1 = 40;//p1 = Person(40);
}
//輸出
Person() - 0x7ffeefbff3f0
Person(int) - 0x7ffeefbff3e8
~Person() - 0x7ffeefbff3e8

4.編譯器自動生成的構造函數

? C++的編譯器在某些特定的情況下,會給類自動生成無參的構造函數,比如

成員變量在聲明的同時進行了初始化

//很多教程都說:編譯器會為每一個類都生成空的無參的構造函數 -----> 這句話是錯的
class Person{
public:
    int m_age = 5;
};
int main(){
    Person person;
    getchar();
    return 0;
}

等價于

class Person{
public:
    int m_age;
    Person(){
        m_age = 5;
    }
};

new Person()初始化成員變量為0,否則Person person在棧空間成員變量默認值為0xcc

有定義虛函數:對象初始化后需要存儲4個字節虛表地址

//很多教程都說:編譯器會為每一個類都生成空的無參的構造函數 -----> 這句話是錯的
class Person{
public:
    int m_age = 5;
    virtual void run(){
        
    }
};
int main(){
    Person person;
}

虛繼承了其他類

//很多教程都說:編譯器會為每一個類都生成空的無參的構造函數 -----> 這句話是錯的
class Person{
public:
    int m_age = 5;
    virtual void run(){
        
    }
};
class Student:virtual public Person{
public:
    int m_score;
};
int main(){
    Person person;
}

包含了對象類型的成員,且這個成員有構造函數(編譯器生成或自定義)

自定義

class Car {
public:
    int m_price;
    Car(){
        
    }
};
class Person {
public:
    Car car;
};
int main(){
    Person person;
}

編譯器生成

class Car {
public:
    int m_price = 0;
};
class Person {
public:
    Car car;
};
int main(){
    Person person;
}

父類有構造函數(編譯器生成或自定義)
父類自定義構造函數

class Person{
public:
    int m_age;
    Person(){
        
    }
};
class Student:public Person{
public:
    
};
int main(){
    Student student;
}

編譯器自動為父類生成構造函數

class Person{
public:
    int m_age = 0;
};
class Student:public Person{
public:
    
};
int main(){
    Student student;
}

? 總結一下

對象創建后,需要做一些額外操作時(比如內存操作、函數調用),編譯器一般都會為其自動生成無參的構造函數

5.友元

? 友元包括友元函數和友元類
? 如果將函數A(非成員函數)聲明為類C的友元函數,那么函數A就能直接訪問類C對象的所有成員
? 如果將類A聲明為類C的友元類,那么類A的所有成員函數都能直接訪問類C對象的所有成員
? 友元破壞了面向對象的封裝性,但在某些頻繁訪問成員變量的地方可以提高性能

未使用友元函數前,訪問成員變量使用get方法

class Point {
private:
    int m_x;
    int m_y;
public:
    int getX(){return m_x;};
    int getY(){return m_y;};
    Point(int x,int y):m_x(x),m_y(y){}
    void display(){
        cout << "(" << m_x <<", " << m_y << ")" << endl;
    }
};
Point add(Point p1,Point p2){
    return Point(p1.getX()+p2.getX(), p1.getY()+p2.getY());
};
int main(){
    
    Point p1(10,20);
    Point p2(20,30);
    
    Point p3 = add(p1, p2);
    p3.display();
    
    getchar();
    return 0;
}
//輸出
(30, 50)

使用友元函數后,友元函數內部可以通過點語法直接訪問類的成員變量不用通過get方法

class Point {
    friend Point add(Point,Point);
private:
    int m_x;
    int m_y;
public:
    int getX(){return m_x;};
    int getY(){return m_y;};
    Point(int x,int y):m_x(x),m_y(y){}
    void display(){
        cout << "(" << m_x <<", " << m_y << ")" << endl;
    }
};
Point add(Point p1,Point p2){
    return Point(p1.m_x+p2.m_x, p1.m_y+p2.m_y);
};
int main(){
    
    Point p1(10,20);
    Point p2(20,30);
    
    Point p3 = add(p1, p2);
    p3.display();
    
    getchar();
    return 0;
}
//輸出
(30, 50)

未聲明為友元函數的方法不能通過點語法直接訪問成員變量


圖片.png

友元類:友元類中所有成員函數都能直接通過點語法訪問友元類中的成員變量

class Point {
    friend class Math;
private:
    int m_x;
    int m_y;
public:
    int getX(){return m_x;};
    int getY(){return m_y;};
    Point(int x,int y):m_x(x),m_y(y){}
    void display(){
        cout << "(" << m_x <<", " << m_y << ")" << endl;
    }
};
class Math{
public:
    Point add(Point p1,Point p2){
        return Point(p1.m_x+p2.m_x, p1.m_y+p2.m_y);
    };
    void test(){
        Point p1(10,20);
        p1.m_x = 10;
    };
};

能不能定義一個類中,一個函數是友元函數,其他函數不是友元函數,并不能,要么是友元類,友元類中的所有成員函數均是友元函數,要么是一個類中的非成員函數(沒有寫在類中的成員函數)聲明為友元函數

6.內部類

? 如果將類A定義在類C的內部,那么類A就是一個內部類(嵌套類)
? 內部類的特點

支持public(外部可以訪問)、protected(本類以及子類可以訪問)、private(僅僅本類能訪問)權限

class Person{
    int m_age;
public:
    class Car{
        int m_price;
    };
};
int main(){
    Person::Car car1;
    Person::Car car2;
    getchar();
    return 0;
}

protected


圖片.png

private


圖片.png

成員函數可以直接訪問其外部類對象的所有成員(反過來則不行)

class Person{
    int m_age;
public:
    class Car{
        int m_price;
        void run(){
            Person person;
            person.m_age = 10;
        }
    };
};
圖片.png

成員函數可以直接不帶類名、對象名訪問其外部類的static成員,一般情況下靜態成員函數和靜態成員變量可以通過類名或對象訪問

class Person{
private:
    static int m_age;
    static void test(){
    };
public:
    class Car{
    private:
        int m_price;
        void run(){
            
//            Person::m_age = 10;
//            Person::test();
//            Person person;
//            person.m_age = 10;
//            person.test();
            
            m_age = 10;
            test();
//            Person person;
//            person.m_age = 10;
        }
    };
};

不會影響外部類的內存布局:內存布局僅僅只有4個字節m_age成員變量,不會有car對象

class Person{
private:
    int m_age;
    void test(){
    };
public:
    class Car{
    private:
        int m_price;
        void run(){
        }
    };
};
int main(){
    Person::Car car1;//內存布局僅僅只有4個字節m_age成員變量,不會有car對象
    getchar();
    return 0;
}

可以在外部類內部聲明,在外部類外面進行定義

class Point {
    class Math{
        void test();
    };
};
void Point::Math::test(){
    
}
或
class Point {
    class Math;
};
class Point::Math{
    void test(){
        
    }
};
或
class Point {
    class Math;
};
class Point::Math{
    void test();
};
void Point::Math::test(){
    
};

7.局部類

? 在一個函數內部定義的類,稱為局部類
? 局部類的特點

作用域僅限于所在的函數內部
其所有的成員必須定義在類內部,不允許定義static成員變量,因為static成員變量必須在函數外部初始化,static成員變量使用前必須初始化,放在類外部初始化,但局部類要求所有成員必須在類里面,矛盾了

void test(){
    //局部類
    class Car{
        void run(){
            
        }
    };
}
圖片.png

成員函數不能直接訪問函數的局部變量(static變量除外(全世界內存只要一份))


圖片.png
void test(){
    static int age = 10;
    //局部類
    class Car{
        void run(){
            age = 11;
        }
    };
}

不會影響test函數的內存布局,除非test函數中調用了生成了Car對象,調用了car對象的成員函數

void test(){
    static int age = 10;
    //局部類
    class Car{
    public:
        void run(){
            age = 11;
        }
    };
    Car car;
    car.run();
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容