C++中的类
类
C++中可以使用struct和class来定义一个类。
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++对象占用的内存空间可能是连续的,顺序由代码顺序决定。为什么说是可能呢,请参考内存对齐。

struct和class的区别
struct的默认成员权限是public。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;
}

不写,并没有看见调用构造函数。
struct Person
{
int id;
int m_age;
int height;
};
int main()
{
Person person;
person.m_age = 10;
return 0;
}

给成员变量一个默认值,调用了构造函数。
struct Person
{
int id;
int m_age = 0;
int height;
};
int main()
{
Person person;
person.m_age = 10;
return 0;
}

应该是:在某些特定的情况下,编译器才会为类生成空的无参的构造函数。
编译器自动生成构造函数
C++的编译器在某些特定的情况下,会给类自动生成无参的构造函数,比如
- 在声明成员变量时初始化。
- 有定义虚函数。
- 虚继承其他类。
- 包含了对象类型的成员,且这个成员有构造函数(编译器生成或自定义)。
- 父类有构造函数(编译器生成或自定义)。
对象创建后,需要做一些额外操作时(比如内存操作、函数调用),编译器一般会为其自动生成无参构造函数。
隐式构造
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;
}
};

对象内部申请的堆空间,由对象内部回收。
类的声明和实现分离
在类中可以只写声明,在类外面实现。用类目::函数名() {}来指定哪个类的某个函数的实现。
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;
};
};
特点
-
支持
public,protected,private权限。 -
成员函数可以直接访问其外部类对象的所有成员(反过来则不行)。
-
成员函数可以直接不带类名、对象名访问其外部类的
static成员。 -
不会影响外部类的内存布局。
-
可以在外部类内部声明,在外部类外面进行定义。
方法一:
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() { }
局部类
在一个函数内部定义的类,称为局部类。
特点
- 作用域仅限于所在的函数内部。
- 其所有的成员必须定义在类内部,不允许定义
static成员变量。 - 成员函数不能直接访问函数的局部变量(
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;
}
};