一. explicit 关键字
explicit 可以将构造函数声明为显式,即禁止隐式转换
什么是隐式转换,它是怎么发生的?
#include <cstdio>
#include <string>
class Person{
private:
int age;
public:
Person(int age)
:age(age){}
public:
int getAge(void) const{
return this->age;
}
};
void show(const Person &person){
printf("I'm %d years old.\n", person.getAge());
}
int main(){
Person me = 5;
show(me);
show(7);
return 0;
}
在这个代码中发生了两次隐式转换,这样的隐式转换有时很方便,但有时会带来意想不到的后果
尽可能地使用 explicit, 因为它禁止编译器执行非预期的隐式转换,除非你有更好的理由允许这种隐式转换
二. const 修饰成员函数
const 修饰成员函数,需要放在成员函数的后面。如果放在开始,就是在修饰成员函数的返回值了。
- const 修饰成员函数,不能修改成员变量
- const 修饰成员函数实际修饰的是该成员函数隐含的 this 指针,即 this 变为了常量指针常量
1. const 对象可以调用非 const 成员函数吗?
不可以,原因在于用 const 所修饰的对象是一个常量,常量中的内容是不可以被修改的,但是普通成员函数是可以修改对象的内容的,二者矛盾,因此无法调用
2. 非 const 对象可以调用 const 成员函数吗?
可以的,因为普通类型的对象既可以修改成员变量的值,也可以选择不去修改
3. const 成员函数内可以调用其它非 const 成员函数吗?
不可以,因为 const 成员函数是只读的,非 const 成员函数可读可写,前后矛盾
4. 非 const 成员函数可以调用 const 成员函数吗?
可以,非 const 成员函数可读可写,const 成员函数只读,可以选择修改也可以选择不修改
三. 拷贝构造函数
1.1 简介
拷贝构造函数通常只有一个参数,指向自身类型的 const 引用
注意不要在拷贝构造函数前加 explicit,没有意义,还会报错
#include <cstdio>
#include <string>
class Person{
private:
int age;
public:
explicit Person(int age)
:age(age){}
Person(const Person &person)
:age(person.age){
printf("I'm copy constructor.\n");
}
public:
void show(void) const{
printf("I'm %d years old.\n", this->age);
}
};
int main(){
Person me(5);
me.show();
Person you(me);
you.show();
return 0;
}
在 linux 下,编译器有时会对构造函数的调用做优化,我们在学习阶段,关闭这个优化
g++ main.cpp -o run -fno-elide-constructors && ./run
如果我们没有为一个类定义拷贝构造函数,那么编译器会为我们定义一个,被称为合成的拷贝构造函数,它只是简单的将参数的成员逐个拷贝到正在创建的对象中。
有时这很方便,但有时却会出现严重的问题,这个我们以后再讲。
1.2 拷贝构造函数什么时候发生调用
拷贝构造函数不仅在我们定义变量的时候会发生,下列情况也会发生
- 将一个对象作为实参传递给一个非引用形参
void Test1(Person person){
person.show();
}
- 从一个返回类型为非引用类型的函数返回一个对象,并且类中未定义移动构造函数时
Person Test2(void){
Person he(7);
return he;
}
这解释了为什么拷贝构造函数自己的参数为什么必须是引用类型,如果参数不是引用类型,则调用永远不会成功。为了调用拷贝构造函数,我们必须拷贝它的实参,但为了拷贝实参,我们又需要调用拷贝构造函数,如此无限循环
四. 拷贝赋值运算符
类不仅可以控制对象如何初始化,类也可以控制其对象如何赋值
#include <stdio.h>
class Person{
private:
int age;
public:
explicit Person(int age)
:age(age){}
Person &operator=(const Person &person){
this->age = person.age;
printf("I'm copy operator =.\n");
return *this;
}
public:
void show(void) const{
printf("I'm %d years old.\n", this->age);
}
};
int main(){
Person me(5);
Person you(10);
you.show();
you = me;
you.show();
return 0;
}
与构造函数一样,如果类未定义自己的拷贝赋值运算符,编译器会为其合成一个。同拷贝构造函数一样,这有时很方便,有时会出现严重的问题。
作业
1. 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
示例 1:
输入: g = [1,2,3], s = [1,1]
输出: 1
解释:你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。所以你应该输出1。
示例 2:
输入: g =[1,2], s = [1,2,3]
输出: 2
解释:你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。你拥有的饼干数量和尺寸都足以让所有孩子满足。所以你应该输出2.