练习
关于类的这部分内容,书上写得比较杂。先把练习给写上,总结到后面写。
7.1.1
完全没有任何封装得情况下,写得Sales_data类得交易处理程序。
#include"pch.h"
#include<iostream>
#include<string>
using std::cin;
using std::cout;
using std::ostream;
using std::istream;
using std::string;
using std::endl;
using std::cerr;
struct Sales_data
{
string bookNo;//书的id
unsigned units_sold=0;//销量
double revenue = 0.0;//总收入
};
int main() {
Sales_data total;
if (cin>>total.bookNo>>total.units_sold>>total.revenue) {
Sales_data trans;
while (cin>>trans.bookNo>>trans.units_sold>>trans.revenue) {
if (total.bookNo==trans.bookNo) {
total.revenue += trans.revenue;
}
else {
cout << total.bookNo << "," << total.units_sold << "," << total.revenue << endl;
total = trans;
}
}
cout << total.bookNo << "," << total.units_sold << "," << total.revenue << endl;
}
else {
cerr << "No data?!" << endl;
}
}
关于类的大概介绍
可以在类内部声明和定义函数,也可以在类内部声明函数,在类外部定义函数,在类的内部定义的函数会隐式的修饰为inline类型。
struct Class_A{
int a;
int b;
int c;
//在内部定义
int get_a(return a; ){
}
};
//在外部定义
int Class_A::get_b(){
return b;
}
int Class_A::get_c() const{
return c;
}
关于this指针
我们定义了一个类,这个类可以有很多个对象,当使用对象.func()访问函数时,编译器怎么知道这个函数中的变量属于哪个对象呢。
我们在使用对象.func(),访问函数时,实际上是用替这个对象访问这个函数。函数中隐藏了一个this指针。这个this指针指向我们调用这个函数的对象。
Class_A a;
a.get_a();
实际可以看成
Class_A::get_a(&a);
所以在成员函数的内部,我们可以直接访问这个对象的数据成员,无需特殊手段,当然我们也可以使用 this->data.
这个this是一个指针,其本身是无法改变的。但是其指向的对象是可以改变的。
即,默认的this是这个类型
Class_A *const this;
那么如果我们使用一个自定义类的常量对象来访问函数时,怎么办?
我们知道一个顶层const的指针是可以改变其指向对象的属性的,所以如果函数是普通的函数,那么常量调用该函数会报错,我们可以为函数添加const,就如上面的get_c()把函数声明为常量成员函数。
这样this就是指向常量的常量指针了,他现在是这个类型。
const Class_A * const this;
就像指向常量的指针一样,他指向的对象也不可以改变,所以常量成员函数中无法改变数据成员的值。
因为this就是一个指向对象的指针,所以我们可以返回this指向的对象,这可以模仿某些运算符的规则,返回运算符左侧对象的左值。
Class_A& get_self(){
return *this;
}
关于类作用域
类本身就是一个作用域,成员函数则定义在类作用域内。
编译器在编译类的时候,首先编译类的数据成员,然后编译类的成员函数,所以成员函数中可以访问所有的数据成员,而无需注意数据成员和成员函数的定义顺序,当然我们写代码一般都是先写数据成员再写成员函数。
练习
7.2 7.3
这里我就将上一节定义的类中加几个成员函数。
struct Sales_data
{
string bookNo;
unsigned units_sold=0;
double revenue = 0.0;
string isbn() const { return bookNo; };
Sales_data& combine(const Sales_data& );
double avg_price()const;
};
Sales_data& Sales_data::combine(const Sales_data& data) {
//bookNo = data.bookNo;
revenue += data.revenue;
return *this;
}
double Sales_data::avg_price() const {
return revenue / units_sold;
}
int main() {
//const Sales_data data;
//data.set_revenue();
Sales_data total;
if (cin>>total.bookNo>>total.units_sold>>total.revenue) {
Sales_data trans;
while (cin>>trans.bookNo>>trans.units_sold>>trans.revenue) {
if (total.isbn()==trans.isbn()) {
total.combine(trans);
}
else {
cout << total.bookNo << "," << total.units_sold << "," << total.revenue << endl;
total = trans;
}
}
cout << total.bookNo << "," << total.units_sold << "," << total.revenue << endl;
}
else {
cerr << "No data?!" << endl;
}
}
7.4 7.5
struct Person {
string name;
string address;
string get_info()const{
return "name:" + name + "\n address:" + address;
}
};
Person person;
person.name = "诸葛大力";
person.address = "爱情公寓";
cout<<person.get_info()<<endl;
定义类相关的非成员函数
有些函数我们声明在类内部,没有声明在类外部的可读性高,所以可以声明在类的外部。
对于IO类的对象,是不允许拷贝的,所以在形参列表中只能定义IO类对象的指针或者引用,另外IO类对象在输入和输出时候,对象内的状态是在改变的,所以使用IO类对象,一般不用声明为常量类型。
练习
7.7
ostream& print(ostream& temp_cout, const Sales_data& data){
temp_cout << data.bookNo << "," << data.units_sold << "," << data.revenue << endl;
return temp_cout;
}
Sales_data add(Sales_data &data1,const Sales_data & data2) {
Sales_data temp = data1;
data1.revenue += data2.revenue;
return temp;
}
struct Person {
string name;
string address;
string get_info()const{
return "name:" + name + "\n address:" + address;
}
};
Sales_data total;
if (input(cin,total)) {
Sales_data trans;
while (input(cin,total)) {
if (total.isbn()==trans.isbn()) {
total.combine(trans);
}
else {
print(cout,total);
total = trans;
}
}
print(cout, total);
}
else {
cerr << "No data?!" << endl;
}
读取和打印可以写在函数内部,但是这样必须先有一个对象,才可以调用。写在函数外面可读性要高一些。
7.8
因为print语句并不会改变Sales_data形参内部的内容
7.9
istream &input(istream& temp_cin, Person& p) {
temp_cin >> p.name >> p.address;
return temp_cin;
}
ostream & print(ostream& temp_cout,const Person &p) {
temp_cout << p.name << "," << p.address << endl;
return temp_cout;
}
7.10
为data1赋值,如果data1的赋值没有出现问题则为data2赋值
7.1.4构造函数
构建类的对象都需要对类的数据成员进行初始化,每个类都可以定义它的对象被初始化的方式,类通过定义一个或者多个特殊的成员函数来进行数据成员的初始化,这些函数叫做构造函数。
只要有类的对象被创建,就会调用类的构造函数,构造函数没有返回值,其函数名和类名一样。可以重载多个构造函数,没有常量构造函数,简单的来说,一个常量对象是在执行了构造函数之后才具备常量的属性。
在一开始我们构造类的时候,并没有显式的写构造函数,编译器会为我们生成一个默认的构造函数。在创建对象时,如果数据成员有类初始值,则使用类初始值进行初始化,如果没有则使用默认初始化,但是默认初始化通常会导致未定义的行为。
一旦我们显式的构造了一个构造函数,那么编译器将不会为我们生成默认构造函数,我们在编程时,最好自己写构造函数。
构造函数可以重载,如果我们想使用编译器默认的构造函数,可以这样定义一个构造函数。
Class_A():=default;
=default表示构造函数使用系统默认的行为。
构造函数初始值列表
在定义构造函数时,我们可以在形参列表和函数体之间,加上:数据成员名(形参名)的形式为数据成员进行初始化。
Class_A(int a,int b,int c):a(a),b(b),c(c){};
这样可以将形参的值直接初始化数据成员。我觉得使用构造函数初始值列表和下面在函数体中直接初始化差不多。
Class_A(int a,int b,int c){
this->a=a;
this->b=b;
this->c=c;
};
但是为什么要出现这个构造函数初始值列表呢?
根据书中的一句话
没有出现在构造函数初始值列表中的成员将通过相应的类内初始值进行初始化,或者执行默认初始化
根据这句话推断,如果构造函数初始值列表为空,那么程序会执行一次初始化,执行完之后,再执行函数体内的内容。所以在函数体中的初始化,其实是赋值.
由下面的类定义可以得到结论,首先定义一个常量数据成员,常量初始化后,不能再改变,定义的构造函数中,需要对这个常量进行初始化,可以看到函数在初始化列表的地方报错了,这表示只有在初始化列表中初始化才是真的初始化。在函数体中的“初始化”其实是赋值。
练习
7.13
这个答案是参考的这个大神写的,因为之前一直使用cin的状态来作为if和while的条件,现在使用输入流的构造函数,此时的cin是在构造函数内部执行的,没有返回值。
所以我就卡住了,不知道直接使用cin对象就可以作为判断的条件。
https://blog.csdn.net/misayaaaaa/article/details/54379643
struct Sales_data;
istream& input(istream& temp_input, Sales_data& data);
struct Sales_data
{
string bookNo;
unsigned units_sold=0;
double revenue = 0.0;
string isbn() const { return bookNo; };
Sales_data& combine(const Sales_data& );
double avg_price()const;
//构造函数
//1.默认构造函数
Sales_data() {};
//2.仅初始化isbn
Sales_data(const string& isbn) :bookNo(isbn) {};
//3.数据成员都初始化
Sales_data(const string& isbn, unsigned u, double p) :bookNo(isbn), units_sold(u), revenue(u*p) {};
//4.使用流来初始化
Sales_data(istream& temp_input) {
input(temp_input,*this);
};
};
Sales_data total(cin);
if (cin) {
Sales_data trans(cin);
do {
if (total.isbn() == trans.isbn()) {
total.combine(trans);
}
else {
print(cout, total);
total = trans;
}
} while (input(cin,trans));
print(cout, total);
}
else {
cerr << "No data?!" << endl;
}
7.15
struct Person {
string name;
string address;
string get_info()const{
return "name:" + name + "\n address:" + address;
}
Person(const string &n, const string &address) :name(n), address(address) {};
};
7.1.5 拷贝、赋值和析构
除了定义类的初始化,我们还需要定义类的拷贝,赋值和析构该怎样进行。
如果我们没有定义,那么编译器会使用一套默认的方式。
具体的内容还要等到书的后面去了。