对象类型的参数和返回值
使用对象类型作为函数参数或者返回值, 可能会产生一些不必要的中间对象
(1)对象类型做函数参数时
void test1(Car car) { // 对象类型作为参数
}
int main() {
Car car1;
test1(car1);
}
把外面的对象传给里面的对象时, 会产生一个新的对象(不必要的中间对象), 因为相当于void test1(Car car = car1)利用一个已经存在的对象, 构建出了一个新的对象, 所以就是拷贝构造.
解决这个问题: 传引用或指针
void test1(Car &car) {
}
int main() {
Car car1;
test1(car1);
}
相当于void test1(Car &car = car1) 就直接引用着外面的car1对象, 这样就不会产生新的对象, 即将car1的地址值传给了car这个引用去保存, 因为引用的本质就是指针, 所以就是地址传递, 不会直接将对象传过去去构建一个新的对象.
(2)对象类型做返回值时:
Car test2() {
Car car;
return car;
}
int main() {
Car car2;
car2 = test2();
}
当把car返回时, 会多出一个拷贝构造, 因为在函数里面定义的car对象的内存在teat2所在的栈空间, 要把test2栈空间里面的对象返回到main函数的栈空间里, 而它的内存在离开函数后就销毁了, 所以要拷贝.
它的做法是:提前将test2里面的car对象拷贝构造出一个新的对象, 而这个新对象的内存是在main函数里面的.
(3)编译器优化
Car test2() {
Car car;
return car;
}
int main() {
Car car3 = test2();
}
只会调用1次拷贝构造, 直接给car3
匿名对象(临时对象)
匿名对象:没有变量名, 没有被指针指向的对象, 用完马上调用析构
(1)编译器优化(了解)
匿名对象作为参数
(2)编译器优化(了解)
直接返回匿名对象
隐式构造
C++里面存在隐式构造的现象 : 某些情况下, 会隐式调用单参数的构造函数
(1)Person p1 = 20;
调用了单参数的构造函数, 等价于Person p1(20);
(2)
void test1(Person person) {
}
int main() {
test1(20);
}
相当于Person person = 20; 调用单参数的构造函数构建对象
(3)
Person test2() {
return 40;
}
int main() {
test2();
}
隐式构造, 调用单参数构造函数构建出对象
(4)
Person p1;
p1 = 40;
只有对象才可以赋值给对象, 所以40会发生隐式构造, 相当于Person(40), 调用单参数的构造函数构建出一个临时对象.
(5)可以通过关键字explicit(明确的意思)来禁止掉隐式构造
class Person {
int m_age;
public:
explicit Person(int age) : m_age(age) {}
};
(6)有些地方也叫转换构造, 因为会将40直接转换成Person对象.
编译器自动生成的构造函数
C++的编译器在某些特定的情况下, 会给类自动生成无参的构造函数, 比如
(1)成员变量在声明的同时进行了初始化
class Person {
public:
int m_price = 5;
}
等价于
class Person {
public:
int m_price;
Person() {
m_price = 5;
}
}
编译器会自动生成构造函数, 初始化是在构造函数中做的.
(2)有定义虚函数
因为一旦有虚函数的话, 这个对象会多出4个字节来存放虚表地址.
在创建完Person对象后, 要给Person对象的最前面的4个字节赋虚表地址.
vftable = virtual function table
(3)虚继承了其他类
class Student : virtual public Person {
}
一旦虚继承了某个类, 那么当时候某个类创建出来的对象最前面4个字节存储着虚表地址
(4)包含了对象类型的成员, 而且这个成员有构造函数(自定义或编译器生成)
class Car {
public:
int m_price;
Car() {}
};
class Person {
Car car;
}
int main() {
Person person;
}
创建完person对象之后, person对象里面要搞一个Car对象, 就要调用Car的无参的构造函数,
所以这个时候编译器就会为Person这个类生成一个构造函数, 在构造函数里面调用Car的 构造函数去初始化car
(5)父类有构造函数(编译器生成或自定义)
class Person {
public:
Person() {}
};
class Student : public Person {
public:
};
因为子类要优先调用父类的构造函数, 所以编译器会为子类自动生成构造函数, 在构造函数里调用父类的构造函数.
总结:对象创建后, 需要做一些额外操作时(比如内存操作(为成员变量赋值), 函数调用), 编译器一般都会为其生成无参的构造函数.
友元
友元函数包括友元函数和友元类
(1)如果将函数A(非成员函数)声明为类C的友元函数, 那么在函数A内部就能直接访问类C对象的所有成员.
class Point {
friend Point add(Point, Point);
}
(2)如果将类A声明为类C的友元类, 那么在类A的所有成员函数内部都能直接访问类C对象的所有成员.
friend class [类名];
内部类
如果将类A定义在类C的内部, 那么类A就是一个内部类(嵌套类)
内部类的特点:
(1)支持public, protected, private 权限
class Person {
public:
class Car {
int m_price;
};
};
int main() {
Person::Car car1;
// 创建了一个car对象
}
(2)内部类的成员函数可以直接访问其外部类对象的所有成员(反过来则不行)
(3)成员函数可以直接不带类名, 不带对象名访问其外部类的static成员
class Point {
private:
static void test1() {
cout << "Point::test1()" << endl;
}
static int ms_test2;
int m_x;
int m_y;
public:
class Math {
public:
void test3() {
cout << "Point::Math" << endl;
test1();
ms_test2 = 10;
Point point;
point.m_x = 10;
point.m_y = 20;
}
};
};
(4)不会影响外部类的内存布局, 仅仅是访问权限变了而已, 内存布局是不变的.(编译器特性)
(5)可以在外部类内部声明, 在外部类外面定义.
局部类
在一个函数内部定义的类称之为局部类.
局部类的特点:
(1)作用域仅限于所在的函数内部
(2)其所有的成员必须定义(声明和实现)在类内部, 不允许定义static成员变量
因为static成员变量时使用前必须放在类外面初始化, 而局部类又要求必须放在里面, 矛盾.
(3)成员函数不能直接访问函数的局部变量(static变量除外)
因为函数的局部变量, 只有在调用函数时才分配存储空间, 那么在调用对象的成员函数访问其外部函数的局部变量时, 该局部变量还没有分配存储空间, 所以报错
在成员函数的栈空间里不能访问其他的栈空间里面的变量.
(4)局部类不影响其外部函数的内存布局.因为类的定义是代码, 存放在代码段, 不会开辟栈空间, 只有在创建对象时, 才会开辟栈空间(前提对象里面有成员变量). 类的定义和执行函数不同.
类放在函数里面或外面仅仅是访问权限的变化.