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();
}

你可能感兴趣的:(012-对象类型的参数和返回值、友元函数、内部类和局部类)