第七章 类
再引前缘:旅途开篇C++ primer 学习之旅——迷茫后启程
7.1 实现Sales_data类后的收获与感悟
- 类的基本思想是数据抽象和封装(private),这也是为什么class的默认权限是private。封装的概念很重要,要重视private,开发中很有用;
- private理解:定义在private之下的成员可以被类成员函数访问,但不能被其他代码访问(当然友元除外)
- 一般类的数据成员都封装在private权限中,public权限用于提供函数接口(让用户通过这些函数来与private中的成员打交道)
- 想单独访问某个数据成员时,可以提供相应的函数接口(没必要放在public作用域下)
inline int Sales_data::show_isbn()
{
return this->isbn;
}
- 没有 unsigned double 类型
- 定义在类内部的函数是隐式inline的。在(头文件中)类中声明函数时不用写inline,因为时默认的(隐式inline);但在函数定义时如果有必要的话(注意是有必要的话,应遵循内联函数规定)应加上inline;注意:内联函数常常定义在(注意,不是声明)头文件中,而不是源文件;
- istream & ostream
istream& my_read(istream& is,Sales_data& book)
{
is >> book.isbn >> book.num >> book.value_sum;
return is;
}
ostream& my_print(ostream& os, const Sales_data& book)
{
os << ' ' << book.isbn << ' ' << book.num << ' ' << book.value_sum;
return os;
}
//细节:输出函数应尽量减少对格式的控制(如换行符endl),这样用户代码的灵活度更高
istream 和 ostream 都属于IO类,而IO类属于不能拷贝类型,所以我们只能通过引用的方式传递它们;
- 大多数情况用 const Sales_data& book,但不要一股脑全用,想清楚const和&什么时候加,什么时候不加;
- 当private中放有数据成员时,应常把权限放心中(friend勿忘记),不然可能编译报错
- 编译器分两步处理类:先编译成员的声明,然后才是成员函数。因此,成员函数体可以随意使用其他成员而无须在意这些成员出现的次序。而且成员函数之间的次序也可以随意(即使相互调用)
this指针 与 const成员函数
- 在成员函数内,任何对类成员的直接访问都被看作对this的隐式引用,即isbn本质上是this->isbn
- this 是一个常量指针,是 Sales_data* const this
- const 成员函数
class Student
{
public:
void show_id() const
{
cout << id << endl;
}
private:
string id;
};
const 是来修饰this指针的,使得在这个函数中this所指向的成员(不管是隐式还是显示)无法修改
- 道高一尺,魔高一丈:mutable 关键字。const成员函数可以改变一个可变成员的值
class Student
{
public:
void change_id(string s) const
{
this->id = s;
}
private:
mutable string id;
};
- 如果成员函数声明时加了const,那么它在定义时也必须在参数列表后加上const
- 可以定义函数使其的返回值为 *this。补充:返回值类型为Student还是Student&是有区别的。前者会拷贝一份 *this,但已不再是从前的那个少年;
构造、析构、拷贝
构造函数(constructor)
- 构造函数(constructor 英文名也应该知道)出现的意义就是来初始化类对象数据成员的(理解这点很重要)
- 构造函数可以有多个;
- 构造函数不能被声明成const的
- 当程序员未定义构造函数时,编译器会自己补一个默认构造函数(无参,且函数体为空,即数据成员未通过构造函数进行初始化)
- 除了构造函数进行初始化外,还可以显示的进行初始化
class Student
{
private:
int age = 17;
string school("八中");
};
- 有一个点需要注意:在创建新类时,尽量确保每个值都能被初始化(不管用哪种方式),不然值是未定义的,很危险!
- 当定义了自己的构造函数后,编译器将不再提供默认构造函数,这时,可能会出一些错误
class Student
{
public:
Student(int age,string school)
{
this->age = age;
this->school = school;
}
private:
int age ;
string school;
};
int main()
{
Student student; //错误,会报错,应为没有默认无参构造函数
Student student2(17,"八中"); //正确
return 0;
//有人可能有这样的疑惑,为什么想调用无参构造时,不写成这样:
//Student student();
//因为编译器不知道你这是函数调用(或函数声明)还是创立新对象
}
- =default的含义:就是默认构造的一种高颜值写法;
//为了防止上面这种错误发生,常常需要自己补一个默认构造或无参构造
class Student
{
public:
Student() = default;
Student(int age,string school)
{
this->age = age;
this->school = school;
}
private:
int age ;
string school;
};
- 构造函数也可以定义在类外部,加作用域即可;
拷贝构造函数
- 默认情况下,编译器会帮我们自动生成拷贝构造函数,大概长成这样:
Student(const Student& s)
{
this->age = s->age;
this->school = s->school;
}
- 也正是有了拷贝构造函数,以下代码才合法:
Student s1(17,"八中");
Student s2 = s1; //等同于 Student s2(s1);
补充:函数返回时,也是类似于 '='赋值
访问控制与封装
- 什么样的成员应该放在public说明符下,哪些又应放在private下?
//一般来说,作为接口的一部分、构造函数和部分成员函数应该定义在public下;
//而数据成员和作为实现部分的函数应该放在private下
友元
- 作用:使类外函数也能访问到private权限下的成员
- 语法
class Student
{
friend int get_age(const Student& student);
public:
Student() = default;
Student(int age,string school)
{
this->age = age;
this->school = school;
}
private:
int age ;
string school;
};
int get_age(const Student& student)
{
return student.age;
}
- 友元声明只能出现在类的内部,但具体在类的哪个位置,没有明确要求。但一般集中放在类的开始或结束位置
- 友元声明仅仅指定了访问的权限,而非通常意义上的函数声明。虽然有的编译器不强制类外应该有友元函数的正式声明,但我们最好加上。即最好在类外先有函数的声明,然后再在类内声明友元;
我的代码仓库:我的仓库