C++中可以使用structclass来定义一个类。

struct Person
{
    int age;
    
    void run()
    {
        // ...
    }
};

class Person1
{
    int age;
    
    void run()
    {
        // ...
    }
};

int main()
{
    // 创建对象
    Person p1;
    Person1 p2;
    p1.age = 10;
    p2.age = 20; // 错误,因为属性权限是private
    
    // 通过指针访问
    Person *p3 = &p1;
    p3->age = 20;
    
    return 0;
}

C++对象占用的内存空间大小取决于成员变量占用内存空间的总和。成员函数只有一份内存,而且这份内存,不属于对象。

C++对象占用的内存空间可能是连续的,顺序由代码顺序决定。为什么说是可能呢,请参考内存对齐

image-20210704142722152

struct和class的区别

  1. struct的默认成员权限是public
  2. class的默认成员全是private

this

this指针存储着函数调用者的地址。

lea eax [对象地址]
call 函数地址
...

只能通过this->成员属性来访问,不可以使用this.成员属性来访问 。

构造函数

构造函数,也叫构造器,在对象创建的时候自动调用,一般用于完成对象的初始化工作。

构造函数与类名同名,无返回值(void都不能写),可以有参数,可以重载,可以有多个构造函数。

一旦自定义了构造函数,必须用其中-一个自定义的构造函数来初始化对象。

通过malloc创建的对象,不会调用构造函数

class Person
{
public:
    int id;
    int age;
    int height;

    Person()
    {
        this->id = 0;
        this->age = 0;
        this->height = 0;
    }

    Person(int age)
    {
        this->age = age;
    }

};

int main()
{
    Person *p1 = new Person;
    cout << p1->age << endl;

    Person *p2 = new Person(20);
    cout << p2->age << endl;

    delete p1;
    delete p2;

    return 0;
}

默认情况下,编译器会为每一个类都生成空的无参的构造函数。

这句话不严谨!

当我们写了构造函数,可以看到调用了构造函数。

struct Person
{
    int id;
    int m_age;
    int height;

    Person() {  }
};

int main()
{
    Person person;
    return 0;
}

image-20210704171801905

不写,并没有看见调用构造函数。

struct Person
{
    int id;
    int m_age;
    int height;
};

int main()
{
    Person person;
    person.m_age = 10;
    return 0;
}

image-20210704171948787

给成员变量一个默认值,调用了构造函数。

struct Person
{
    int id;
    int m_age = 0;
    int height;
};

int main()
{
    Person person;
    person.m_age = 10;
    return 0;
}

image-20210704172257348

应该是:在某些特定的情况下,编译器才会为类生成空的无参的构造函数。

编译器自动生成构造函数

C++的编译器在某些特定的情况下,会给类自动生成无参的构造函数,比如

  1. 在声明成员变量时初始化。
  2. 有定义虚函数。
  3. 虚继承其他类。
  4. 包含了对象类型的成员,且这个成员有构造函数(编译器生成或自定义)。
  5. 父类有构造函数(编译器生成或自定义)。

对象创建后,需要做一些额外操作时(比如内存操作、函数调用),编译器一般会为其自动生成无参构造函数。

隐式构造

C++中存在隐式构造的现象:某些情况下,会隐式调用单参数的构造函数。

class Car
{
    int p;
public:
    Car()
    {
        printf("Car::Car() --- %X\n", this);
    }
    
    Car(int p) : p(p) {  }

    Car(const Car &c)
    {
        printf("Car::Car(const Car &c) --- %X\n", this);
    }
};

Car test()
{
    return 70;	// 这里会变成:return Car(70);
}

可以通过关键字explicit禁止掉隐式构造。

class Car
{
    int p;
public:
    Car()
    {
        printf("Car::Car() --- %X\n", this);
    }
    
    explicit Car(int p) : p(p) {  }

    Car(const Car &c)
    {
        printf("Car::Car(const Car &c) --- %X\n", this);
    }
};

Car test()
{
    return 70;	// 这里会变成:return Car(70);
}

初始化列表

一种便捷的初始化成员变量的方式。

只有构造函数才能使用。

void func() { return 2; }

struct A
{
    int a;
    int b;
    // A(int a, int b)
    // {
    //     this->a = a;
    //     this->b = b;
    // }
    // 等价于上面的写法,本质就是上面的
    // A(int a, int b) : a(a), b(b) {  }
    // 可以对a进行操作
    A(int a, int b) : a(a + 2), b(b) {}
    // 也可以放函数
    // A(int a, int b) :a(func()), b(b) {  }
};

注意:初始化的时候,只跟成员定义的顺序有关,与冒号后面的顺序无关。

跟默认参数一起使用

struct A
{
    int a;
    int b;
    A(int a = 0, int b = 0) : a(a), b(b) {}
};

int main()
{
    A a1;
    A a2(1);
    A a(1, 2);

    return 0;
}

如果函数声明和实现是分离的,初始化列表只能写在函数的实现中。

struct A
{
    int a;
    int b;
    int c;
    A(int a = 0, int b = 2);
};

A::A(int a, int b) : a(a), b(b) 
{

}

构造函数互相调用

构造函数调用其他的构造函数必须放在初始化列表中。

struct A
{
    int a;
    int b;

    // 这样是错误的
    // A()
    // {
    //     A(10, 20);
    // }

    
    A() :A(10, 20) 
    {

    }

    A(int a, int b)
    {
        this->a = a;
        this->b = b;
    }

};

int main()
{
    A a;
    printf("%d, %d", a.a, a.b);
    return 0;
}

析构函数

析构函数(也叫析构器), 在对象销毁的时候自动调用,一般用于完成对象的清理工作。

析构函数名以~开头,与类同名,无返回值(void都不能写),无参,不可以重载,有且只有一个析构函数。

通过malloc分配的对象free的时候不会调用析构函数。

构造函数、析构函数要声明为public,才能被外界正常使用。

struct Person
{
    int id;
    
    Person()
    {
        this->id = 0;
    }

    ~Person()
    {
        // ...
    }
};

具体的用途:内存管理

可以使用析构函数来释放函数内部用到的内存空间。

class Car
{
public:
    double price;
};

class Person
{
public:
    int age;
    Car *car;

    Person()
    {
        // 构造函数初始化car
        this->car = new Car();
    }

    ~Person()
    {
        //  在析构函数中清理car的内存
        delete this->car;
    }

};

image-20210711185806214

对象内部申请的堆空间,由对象内部回收。

类的声明和实现分离

在类中可以只写声明,在类外面实现。用类目::函数名() {}来指定哪个类的某个函数的实现。

class Person
{
private:
    int age;

public:
    void set_age(int);

    int get_age();

    Person();

    ~Person();
};

void Person::set_age(int age)
{
    this->age = age;
}

int Person::get_age()
{
    return this->age;
}

Person::Person()
{
    // ...
}

Person::~Person()
{
    // ...
}

这样声明类就可以放到.h文件中,实现就可以放到.cpp文件中。

// person.h
#paragma once
class Person
{
private:
    int age;

public:
    void set_age(int);

    int get_age();

    Person();

    ~Person();
};
// person.cpp
#include "person.h"

void Person::set_age(int age)
{
    this->age = age;
}

int Person::get_age()
{
    return this->age;
}

Person::Person()
{
    // ...
}

Person::~Person()
{
    // ...
}

内部类

如果将类A定义在类C的内部,那么类A就是一个内部类(嵌套类)。

class A
{
    class B
    {
        int b;
    };
};

特点

  1. 支持publicprotectedprivate权限。

  2. 成员函数可以直接访问其外部类对象的所有成员(反过来则不行)。

  3. 成员函数可以直接不带类名、对象名访问其外部类的static成员。

  4. 不会影响外部类的内存布局。

  5. 可以在外部类内部声明,在外部类外面进行定义。

    方法一:

    class A
    {
        class B
        {
            void test();
        };
    };
    
    void A::B::test()
    {
        
    }

    方法二:

    class A
    {
        class B;
    };
    
    class A::B
    {
        void test() {}
    };

    方法三:

    class A
    {
        class B;
    };
    
    class A::B
    {
        void test();
    };
    
    void A::B::test()
    {
        
    }

局部类

在一个函数内部定义的类,称为局部类。

特点

  1. 作用域仅限于所在的函数内部。
  2. 其所有的成员必须定义在类内部,不允许定义static成员变量。
  3. 成员函数不能直接访问函数的局部变量(static变量除外)。
void test()
{
    class A
    {
        
    };
}

函数声明

以为下面两条Person person();Person p();是创建Person对象?不是的

struct Person
{
    int id;
    int age;
    int height;
};
// 这里是创建Person对象?这是函数的声明
Person person();

int main()
{
    // 这里是创建Person对象?这是函数的声明
	Person p();
    return 0;
}

对象型参数和返回值

使用对象类型作为函数的参数或者返回值,可能会产生一-些不必要的中间对象。

class Car
{
public:
    Car()
    {
        printf("Car::Car() --- %X\n", this);
    }

    Car(const Car &c)
    {
        printf("Car::Car(const Car &c) --- %X\n", this);
    }
};

void test(Car c) // 这里又创建了一个对象
void test(Car &c) // 可以写引用(指针)来解决
{

}

int main()
{
    Car c;
    test(c);
    return 0;
}
// 输出
// Car::Car() --- 7BFE1E
// Car::Car(const Car &c) --- 7BFE1F

匿名对象(临时对象)

class Car
{
public:
    Car()
    {
        printf("Car::Car() --- %X\n", this);
    }

    Car(const Car &c)
    {
        printf("Car::Car(const Car &c) --- %X\n", this);
    }
};

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

没有变量名、没有被指针指向的对象,用完后马上调用析构。

友元

友元包括友元函数和友元类。

如果将函数A (非成员函数)声明为类C的友元函数,那么在函数A内部就能直接访问类C对象的所有成员。

class Point
{
    // 声明友元函数
    friend void add(Point&, Point&);
    // 友元类
    friend class Math;
private:
    int x, y;
public:
    int get_x() { return this->x; }
    int get_y() { return this->y; }
    Point(int x, int y) : x(x), y(y) {}
};

void add(Point& p1, Point& p2)
{
    // 友元函数中,可以直接访问私有成员。
    p1.x += p2.x, p1.y += p2.y;
}

class Math
{
    void add(Point& p1, Point& p2)
    {
        // 友元类中,可以直接访问私有成员。
        p1.x += p2.x, p1.y += p2.y;
    }
};