第六章因为其他的工作耽误了进度,很久才把那一章看完,终于来到了第七章类,这一章看起来内容较多,但也是c++关键的部分。在c++语言中使用类定义自己的数据类型。
第21页的练习是:编写程序,读取多条销售记录,并统计每个ISBN(每本书)有几条销售记录。 输入表示多个ISBN的多条销售记录来测试上一个程序,每个ISBN的记录应该聚在一起。
C++推荐书籍:the C++ standard library。
看视频时的一些记录:什么是面向对象呢?数据和对数据的操作封装在了一起?
7.1.2 定义改进的Sales_data类
先看一个例子:
struct Sales_data{
std::string isbn() const { return bookNo; }//this->bookNo
Sales_data& combine(const Sales_data&);
double avg_price() const;
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
}
引入this:this的目的总是指向“这个”对象,所以this是一个常量指针。不允许改变this中保存的地址。
类似于常量引用,指向常量的指针不能用于改变其所指对象的值(是底层const,指向的对象是常量)。要想存放常量对象的地址,只能使用指向常量的指针。一般来说,指针的类型必须和其所指对象的类型一致,但是有两种例外情况。其中一种是允许一个指向常量的指针指向一个非常量对象。??
允许把指针本身定义为常量,常量指针必须初始化,而且一旦初始化它的值就不能再改变。
默认情况下,this是指向类类型的非常量版本的常量指针。(常量指针指this中保存的地址不能变了,指向的是非常量),this的类型是Sales_data *const.但在这个前提下,把this设置为指向常量的指针有助于提高函数的灵活性,即改为const Sales_data *const(尽可能的使用const?). 紧跟在参数列表后面的const表示this是一个指向常量的指针,像这样使用const的成员函数被称作常量成员函数。这个成员函数被称作常量成员函数。isbn可以读取调用它的对象的数据成员,但是不能写入新值。
注意:常量对象,以及常量对象的引用或指针都只能调用常量成员函数。成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义。在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。
回顾:引用返回左值,函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值,其他返回类型得到右值。
7.1.3 定义类相关的非成员函数
这一部分实际上不属于类本身。注意在print和read函数中,因为读取和写入的操作会改变流的内容,所以两个函数接受的都是普通引用,而非对常量的引用。
7.1.4 构造函数
每个类都定义了它的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数。
回顾:默认初始化:定义于任何函数体之外的变量被初始化为0。定义在函数体内部的内置类型变量将不被初始化,一个未被初始化的内置类型变量的值是未定义的。
构造函数初始值列表:它负责为新创建的对象的一个或几个数据成员赋初值。
原来之前用到的ListNode类中的就是构造函数。
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
在类的外部定义构造函数:
类内初始值,默认初始值。
7.1.5 拷贝,赋值和析构
除了定义类的对象如何被初始化之外,类还需要控制拷贝,赋值和销毁对象时发生的行为。
使用class和struct定义类的唯一区别就是默认的访问权限。定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private部分封装了类的实现细节。
练习:编写一个构造函数,令其用我们提供的类内初始值显式地初始化成员。
- 封装有两个重要的优点:确保用户代码不会无意间破坏封装对象的状态
- 被封装的类的具体实现细节可以随时改变,而无需调整用户级别的代码。
这个写的很好,可以把类的定义放在头文件中,https://www.runoob.com/w3cnote/cpp-header.html
2021.4.11 新标准C++第十一到第十二章一共有40页,计划分4天看完。即一天要看10页。
面向对象的程序设计有抽象,封装,继承,多态4个基本特点。 对象的特点包括两个方面:属性和方法
将数据和用以操作数据的那些算法捆绑在一起。
在编写一个类的时候可以用现有的类作为基础
多态指的是不同种类的对象都具有名称相同的行为,而具体的实现方式却有所不同。
11.4
成员函数作用在某个对象a上,指的是进入该成员函数时,函数访问到的成员变量是属于a的
之前写的类相关的程序
#include <iostream>
#include <string>
#include "Sales_data.h"
using namespace std;
Sales_data::Sales_data(std::istream& is) {
//read函数的作用是从is中读取一条交易信息然后存放到this对象中
read(is, *this);
}
Sales_data& Sales_data::combine(const Sales_data& rhs) {
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;//返回调用该函数的对象;this的类型是Sales_data *const this;只有返回的是引用类型返回的才是一个左值
}
//输入的信息包括ISBN,售出总数和售出单价
istream& read(istream& is, Sales_data& item)
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = item.units_sold * price;
return is;
}
ostream& print(ostream& os, const Sales_data& item) {//注意这里的const
os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();//cout<<。。。
return os;
}
class Person {
public:
//新增的构造函数
Person() = default;
Person(const std::string& s1, const std::string& s2) : name(s1), address(s2) {};
private:
string name;
string address;
string isname() const { return name; }
string isaddress() const { return address; }
};
int main() {
Sales_data total;//保存当前求和结果的变量,后面再有一个trans保存下一条交易数据
if (read(cin, total)) {
Sales_data trans;
while (read(cin, trans)) {
if (trans.isbn() == total.isbn()) {
total.combine(trans);//total+=trans
}
else {
print(cout, total);
total = trans;
}
print(cout, total);
}
}
else {
cerr << "No data!?" << endl;
}
}
7.2.1友元函数
类可以允许其他类或函数访问它的非公有成员,方法是令其他类或者函数成为它的友元。
12.1构造函数
面向对象的程序设计语言倾向于对象一定要初始化后,使用起来才比较安全。构造函数就是一类特殊的成员函数,其名字和类一样,不写返回值类型,可以重载。对象在生成的时候,一定会自动调用某个构造函数进行初始化,对象一旦生成,就再也不会在其上执行构造函数。构造函数执行时对象的内存空间已经分配好了,构造函数的作用是初始化这片内存空间。
只有当类没有声明任何构造函数时编译器才会自动的生成默认构造函数。一个类为什么不一定有默认构造函数呢?
全局变量在程序装入内存时就已经分配好空间,程序运行期间其地址不变。局部变量定义在函数内部,其存储空间是动态分配在栈上的。函数被调用时,栈上会分配出一部分空间存放该函数中的局部变量(包括参数),这片新分配的存储空间里原来的内容是什么,局部变量的初始内容也就是什么,因此局部变量的初始值就是不可预测的了。函数调用结束后,局部变量占用的存储空间就被挥手,以便分配给下一次函数调用中所涉及的局部变量。
12.1.3 复制构造函数
默认构造函数(即无参构造函数)不一定存在,但复制构造函数总是会存在。
复制构造函数也是构造函数的一种,它只有一个参数:本类的引用。
- 复制构造函数被调用的三种情况:当用一个对象去初始化同类的另一个对象时。(赋值语句不会引发复制构造函数调用)
- 作为形参的对象,是用复制构造函数初始化的,而且调用复制构造函数时的参数,就是调用函数时所给的实参。
- 作为函数返回值的对象,是用复制构造函数初始化的,而调用复制构造函数时的实参就是return 语句所返回的对象。
12.1.3类型转化构造函数
除了复制构造函数之外,只有一个参数的构造函数一般都可以称为类型转换构造函数,因为这样的函数可以起到类型自动转换的作用。
12.2 析构函数
析构函数在对象消亡时自动被调用。
12.4 静态成员变量和静态成员函数
静态成员变量一共就一份,被所有同类对象共享,本质是一个全局变量。而静态成员函数并不具体作用在某个对象上,本质是一个全局函数。
静态成员函数不具体作用于某个对象,所以静态成员函数内部不能访问非静态成员变量(因为不知道这个成员变量是属于谁的,是哪个对象的,无法解释),也不能调用非静态成员函数。
为什么静态成员函数也不能调用非静态成员函数呢?(我的理解:静态函数没有一个具体的作用对象,而普通的成员函数可能需要作用在一个具体的对象上,并且还可能会修改对象的值)
常量成员函数内部也不允许调用同类的其他非常量成员函数(静态成员函数除外)。
12.5 常量对象和常量成员函数
构造函数列表那块丢失了些内容