Snowming04's Blog
一颗红❤
Toggle navigation
Snowming04's Blog
主页
Cobalt Strike
Accelerated C++
区块链安全
友链
关于我
常用工具
代码积累
归档
标签
【Accelerated C++】课时9:定义新类型
? C++ ?
2020-04-02 17:46:09
514
0
0
snowming
? C++ ?
C++ 的类型可以分为两个种类:**内部类型**和**自定义类型**。 - 内部类型是被定义成语言核心的一部分的,包括了`char`、`int`以及`double`。 - 库的类型,例如`string`、`vector`、`list`和`stream`都是自定义类型。 # 0x01 自定义类型 自定义类型:自定义类型是一种用来把相关的数据值组合在一个数据结构中的技术,有了这种技术,我们就能把这个数据结构当作一个单独的实体来处理。 比如`Student_info`结构: ``` struct Student_info{ std::string name; double midterm,final; std::vector<double> homework; }; ``` 使用这个类型的问题在于:程序员可以(而且必须)直接地操作这些数据元素。程序员之所以可以直接地操作数据,是因为Student_info的定义并没有限制对数据元素的访问:之所以必须这样做,是因为对于`Student_info`并没有其他可用的操作。 我们不打算让用户直接访问数据,相反我们希望把 `Student_info` 对象的存储方式的实现细节隐藏起来。特别地,我们要求这个类型的用户仅仅通过函数来访问对象。为此,我们首先要向用户提供方便的对`Student_info`对象的操作。这些操作将构成我们的类接口。 注:这个`Student_info`结构一般是定义在头文件中的,所以要使用完整的限定名。 ## 成员函数 为了控制对 `Student_info` 对象的访问,我们需要定义一个可供程序员使用的接口。下面定义一些操作来读一条记录并计算总成绩: ``` struct Student_info { std::string name; double midterm, final; std::vector<double> homework; std::istream read(std::istream); //新增的 double grade() const; //新增的 }; ``` - 在 grade 成员函数的声明中的 const 确保了对 grade 函数的调用将不会修改 Student_info 对象的任何数据成员。 - grade 函数是一个成员函数。从本质上说,一个成员函数是类对象的一个成员。为了调用一个成员函数,我们的用户必须指明被调用的函数是对象的一个成员。因此,我们的用户会对一个名为 s 的 Student_info 对象调用 s.read(in) 或 s.grade()。 - s.read(in) 调用从标准输入读数值并适当地设置 s 的状态。s.grade() 调用则会为 s 计算并返回总成绩。 ## read 成员函数 之前的 read 函数: ``` istream& read(istream& is, Student_info& s) { //读入并存储学生的姓名以及期中、期末考试成绩 is >> s.name >> s.midterm >> s.final; read_hw(is, s.homework); //读入并存储学生的所有家庭作业成绩 return is; } ``` > 注: >- 类型:`T&`:表示对类型`T`的一个引用,它通常被用来传递一个可以由函数修改的参数,与这种参数对应的参数必须是左值。 - 类型:`const T&`:表示对类型`T`的一个引用,而且我们不能对这个引用的值进行修改。我们可以用它来避免复制函数参数所产生的开销。 修改为成员函数的read函数的定义: ``` istream& Student_info::read(istream& in) { in >> name >> midterm >> final; readd_hw(in, homework;) return in; } ``` 我们将会把这些函数放置在一个名为`Student_info.cpp`的源文件中。重要的是,这些函数的声明现在是我们的`Student_info`结构的一部分,因此它们对`Student_info`类的所有用户都必须是可用的。 变成成员函数之后,和之前函数的区别在于: 1. 函数名是`Student_info::read`而不是`read`。 2. 因为这个函数是 Student_info 对象的一个成员,所以我们不需要把一个 Student_info 对象作为参数传递,而且我们根本不需要定义一个 Student_info 对象。 3. 我们直接地访问对象的数据元素。不再是`s.midterm`而是直接引用`midterm`。 对于第1点,在这个函数名中的`::`是一个作用域运算符。通过编写`Student_info::read`,我们就定义了名为 read 的函数,这个函数是`Student_info`类型的一个成员。 在 read 内部引用成员是无需使用限定形式的。这是因为,我们引用的是哪个正在由我们操作的对象的成员。换句话说,如果我们对一个名叫 s 的 Student_info 对象调用 s.read(in),那么我们就是在操作对象 s。当我们在 read 使用 midterm、final 以及 homework 的时候,实际上我们就是在分别使用 s.midterm、s.final 以及 s.homework。 ## grade 成员函数 grade 成员函数的定义: ``` double Student_info::grade() const { return ::grade(midterm, final, homework); } ``` 类似于 read 函数,这里我们把 grade 定义成 Student_info 的一个成员,这个函数隐含地(而不是明确地)引用了一个 Student_info 对象,而且它不需要使用限定形式来访问这个对象的成员。 另外两个值得注意的点是: 1. 对`::grade`的调用。如果我们把`::`放在一个名称之前,那就表明了我们要使用这个名称的某一个版本,而所使用的这个版本不能是任何事物的成员。这样的话,我们希望在这个调用中使用另一个定义的 grade 函数——这个版本的 grade 函数有两个 double 类型的参数和另一个 vector<double> 类型的参数,那我们就要使用 `::`。否则,编译程序将会认为我们所指的是`Student_info::grade`,而且它会提示出错——因为我们在调用时使用了过多的参数。 2. 我们在 grade 的参数列表后面使用了 const。把新的函数声明和原先的那个比较一下,我们就能理解这个用法: ``` double Student_info::grade() const {...} //成员函数版本 double grade(const Student_info&){...} //以前的版本 ``` 在原先的函数中,在传递 Student_info 的时候我们把它当作一个 const(常量)引用。这样我们就确保了我们能够请求带有 const Student_info 对象的 grade 函数,而且如果这个 grade 函数试图修改它的参数(也就是 Student_info 的内容),编译程序就会提示出错。 > 注: >- 类型:`T&`:表示对类型`T`的一个引用,它通常被用来传递一个可以由函数修改的参数,与这种参数对应的参数必须是左值。 - 类型:`const T&`:表示对类型`T`的一个引用,而且我们不能对这个引用的值进行修改。我们可以用它来避免复制函数参数所产生的开销。  也就是说:因为 grade 是 Student_info 的成员函数,所以不会把 Student_info 作为参数传入 grade 函数,即无法给 Student_info 添加 const 限定词来确保其不被修改。这样的情况下,我们可以通过对成员函数本身作限制,让其成为一个「常量成员函数」。 做法就是:在声明及定义时候,在函数参数后面加一个 `const` 限定词。 作用是:常量成员函数不可以修改它归属的对象:如果我们对一个名叫 s 的 Student_info 对象调用了 s.grade(),那么我们就要确保这样做不会修改 s 的数据成员。 ## 非成员函数  compare 函数不会改变 Student_info 对象的状态。并且其工具性强。因此我们将让 compare 函数作为一个全局函数来实现它。 # 0x02 保护  ## C++ 的数据隐藏机制 ``` class Student_info { public: //类型提供的接口 double grade() const; std::istream& read(std::istream&); private: //类型的实现 std::string name; double midterm, final; std::vector<double> homework; }; //不要忘记; ``` 为了达到「把数据隐藏起来、仅允许用户通过我们的成员函数访问这些数据」的目的,对 Student_info 作了两个改动: **1、 使用 class 代替 struct。**使用 struct 和 class 的唯一差别是:如果在第一个保护标识符(`public`或`private`)之前有成员,那么对于 struct 和 class 来说,应用于这些成员的默认保护方式是不同的。如果我们使用 class Student_info,那么在第一个`{`和第一个保护标识符之间的所有成员都是私有的。相反,如果我们编写了 struct Student_info,那么在`{`和第一个保护标识符之间声明的所有成员都是公有的。例如: ``` class Student_info { public: double grade() const; //...省略剩下语句 }; ``` 等价于: ``` struct Student_info { double grade() const; //缺省为公有 }; ``` 同时, ``` class Student_info { std::string name; //缺省为私有 //其他私有成员 public: double grade() const; //其他公有成员 }; ``` 等价于: ``` struct Student_info { private: std::string name; //缺省为私有 //其他私有成员 public: double grade() const; //其他公有成员 }; ```  **2、 我们增加了两个保护标识符。**保护标识符就是`public`、`private`。  ## 存取器函数 需求:现在我们已经隐藏了数据成员,这样用户就必须通过成员函数来设置数据成员或者操作数据成员。但是我们还必须提供另外的一个操作:我们必须设法让用户可以访问到学生的姓名。对于现在已经被设为`private`的`name`属性,我们希望允许读访问,不允许写访问。 ``` class Student_info { public: double grade() const; std::istream& read(std::istream&); std::string name() const {teturn n;} //新增的 public: std::string n; //就是原name double midterm, final; std::vector<double> homework; }; ```  我们会在类定义以外的地方完成对 grade 和 read 的定义。  **<u>compare 函数</u>** 增加了 name 成员函数之后,就可以来编写 compare 函数了: ``` bool compare(const Student_info& x, const Student_info& y) { return x.name() < y.name(); } ``` >注:class 在使用上跟 struct 几乎没有区别。  ## 检查对象是否为空 ``` class Student_info { public: bool valid() const { return !homework.empty(); } }; ``` >注:可以在类的定义里定义函数,也可以在类定义以外的地方完成对函数的定义,比如 grade 和 read 函数。 在调用 grade 之前,用户可以通过这个函数检查对象是否为空从而避免了一个潜在的异常。 # 0x03 Student_info 类 目前的 Student_info 类: ``` class Student_info { public: //有的只写了声明,有的有具体定义 std::string name() const {return n;} bool valid() const {return !homework.empty();} std::istream& read(std::istream); double grade() const; private: std::string n; double midterm, final; std::vector<double> homework; }; bool compare(const Student_info&, const Student_info&); ```  # 0x04 构造函数  **<u>如果我们不定义任何构造函数将会发生什么?</u>** 遵循三条规则:    **<u>Student_info 类构造函数的声明</u>**  ## 缺省构造函数 注:这是上面声明的构造函数之一、不带参数,也是缺省构造函数。  **<u>构造函数初始化程序</u>**  **<u>理解构造函数初始化程序</u>**   ## 带参数的构造函数 注:这是上面声明的构造函数之一、带参数,不是缺省构造函数。  # 0x05 本章小结   # 0x06 定义新类型之后的源码 ## 程序结构:  ## 代码截图: `Student_info.h`:  `Student_info.cpp`:   `main.cpp`:  ## 程序源码: `Student_info.h`: ``` #ifndef GUARD_Student_info_h #define GUARD_Student_info_h #include <string> #include <iostream> #include <vector> #include <stdexcept> #include <algorithm> class Student_info { public: double grade() const; std::istream& read(std::istream&); std::string name() const { return n; } bool valid() const { return !homework.empty(); } Student_info(); //构造一个空的 Student_info 对象 Student_info(std::istream&); private: std::string n; double midterm, final; std::vector<double> homework; }; #endif // !GUARD_Student_info_h; ``` `Student_info.cpp`: ``` #include <string> #include <iostream> #include <vector> #include <stdexcept> #include <algorithm> #include "Student_info.h" using namespace std; istream& read_hw(istream& in, vector<double>& hw); double grade(double midterm, double final, const vector<double>& hw); double median(vector<double> vec); double grade(double midterm, double final, double homework); double median(vector<double> vec){ typedef vector<double>::size_type vec_sz; vec_sz size = vec.size(); if (size == 0) throw domain_error("median of an empty vector"); sort(vec.begin(), vec.end()); vec_sz mid = size / 2; return size % 2 == 0 ? (vec[mid] + vec[mid - 1]) / 2 : vec[mid]; } double grade(double midterm, double final, const vector<double>& hw){ if (hw.size() == 0) throw domain_error("Student has done no homework"); return grade(midterm, final, median(hw)); } double grade(double midterm, double final, double homework){ return 0.2 * midterm + 0.4 * final + 0.4 * homework; } Student_info::Student_info() :midterm(0), final(0) {} Student_info::Student_info(std::istream& is) { read(is); } //读一个流从而构造一个对象 double Student_info::grade() const { return ::grade(midterm, final, homework); } istream& Student_info::read(istream& in){ in >> n >> midterm >> final; read_hw(in, homework); return in; } istream& read_hw(istream& in, vector<double>& hw){ if (in){ //清除原先的内容 hw.clear(); //读家庭作业成绩 double x; while (in >> x) hw.push_back(x); //清除流以使输入动作对下一个学生有效 in.clear(); } return in; } ``` `main.cpp`: ``` #include <string> #include <iostream> #include <iomanip> #include <vector> #include <stdexcept> #include <algorithm> #include "Student_info.h" using namespace std; bool compare(const Student_info& x, const Student_info& y); bool compare(const Student_info& x, const Student_info& y) { return x.name() < y.name(); } int main() { vector<Student_info> students; Student_info record; string::size_type maxlen = 0; //读并存储数据 while (record.read(cin)) { maxlen = max(maxlen, record.name().size()); students.push_back(record); } //按字母顺序排列学生记录 sort(students.begin(), students.end(), compare); //输出姓名和成绩 for (vector<Student_info>::size_type i = 0; i != students.size(); ++i) { cout << setw(maxlen + 1) << students[i].name(); try { double final_grade = students[i].grade(); streamsize prec = cout.precision(); cout << setprecision(3) << final_grade << setprecision(prec) << endl; //setw和setprecision属于<iomanip> } catch (domain_error e) { cout << e.what() << endl; } } return 0; } ```
上一篇:
【Accelerated C++】课时10:管理内存和低级数据结构
下一篇:
【Accelerated C++】课时8:编写泛型函数
0
赞
514 人读过
新浪微博
微信
腾讯微博
QQ空间
人人网
提交评论
立即登录
, 发表评论.
没有帐号?
立即注册
0
条评论
More...
文档导航
没有帐号? 立即注册