前言
介绍了类的基本内容。
chapter 7 class
#include <iostream>
#include <string>
using std::istream;
using std::ostream;
using std::string;
class Sales_data{
//友元声名,并非函数声名,可以在友元声名的同时定义该函数,也必须声明过
friend Sales_data add(const Sales_data&, const Sales_data&);
friend istream & read(istream&, Sales_data&);
friend ostream & print(ostream&, const Sales_data&);
public:
// 新的构造函数
Sales_data() = default;
Sales_data(const string &s):bookNo(s){}
Sales_data(const string &s, unsigned n, double p):
bookNo(s), units_sold(n), revenue(p*n) {}
// 构造函数内部声名,外部定义
Sales_data(istream&is);
// 成员函数,声名必须在内部,定义不要求
// 定义在类内部,隐式的inline函数
// 编译器分两步处理类:1.编译成员声名 2.成员函数体
string isbn() const {return bookNo;}
Sales_data& combine(const Sales_data&) ; // 常量成员函数
private:
double avg_price() const;
// 数据成员
string bookNo;
unsigned int units_sold = 0;
double revenue = 0;
};
// 非成员接口函数,声名应该与类在同一个头文件内
Sales_data add(const Sales_data&, const Sales_data&);
ostream &print(ostream&, const Sales_data&);
istream &read(istream&, Sales_data&);
// 定义在类外部的成员函数
double Sales_data::avg_price() const{
if (units_sold){
return revenue / units_sold;
}else{
return 0;
}
}
Sales_data& Sales_data::combine(const Sales_data&rhs){
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
// 非成员接口函数 定义
istream & read(istream&is, Sales_data&item){
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
// IO类不能被拷贝
ostream & print(ostream&os, const Sales_data&item){
os << item.isbn() << " " << item.units_sold << " "
<< item.revenue << " " << item.avg_price();
return os;
}
Sales_data add(const Sales_data&item1, const Sales_data&item2){
Sales_data sum = item1; //拷贝类的对象
return sum.combine(item2);
}
// 构造函数内部声名,外部定义
// 构造函数没有返回类型
// 因为函数名和类名相同,所以它是构造函数
Sales_data::Sales_data(istream &is){
read(is, *this); //read,从is中读取一条交易信息然后
//存入this对象中
}
int main(){
Sales_data total;
if (read(cin, total)){
Sales_data trans;
while (read(cin, trans)){
if (total.isbn() == trans.isbn()){
total.combine(trans);
}else{
print(cout, total) << endl;
total = trans;
}
}
print(cout, total) << endl;
}else{
cout << "No data?!" << endl;
}
// Sales_data item1 = {"jskf", 4, 89};
// print(cout, item1) << endl;
}
构造函数
相当于 __init__,
构造函数初始值列表
//构造函数初始值列表
Sales_data(const std::string&s, unsigned n, double p):
bookNo(s), units_sold(n), revenue(p*n) {}
// : bookNo(s), units_sold(n), revenue(p*n) 称为 构造函数初始值列表
this的类型是指向类类型非常量版本的常量指针,它指向的对象是一个非常量。在形参列表后添加const代表this指向的是一个常量。
类内初始值必须以符号 = 或者花括号表示。
类的声名和定义
类的对象被创建时必须完全定义类,类声名后即可有指向类的指针或引用
class Y;
class X{
public:
Y *haha;
};
class Y{
public:
X haha;
};
friend可以时函数、类、类的成员函数
友元不具传递性
友元仅仅影响函数权限
7.3.4节练习
class WindowMgr{
private:
std::vector<Screen> screens{Screen(24, 80, ' ')};
public:
void clear(Screen &);
};
class Screen{
friend void WindowMgr::clear(Screen &);
public:
using pos = std::string::size_type;
// typedef std::string::size_type pos;
Screen(/* args */)=default;
// ~Screen();
Screen(pos a, pos b, char c):
contents(a*b, c), height(a), width(b){}
// Screen &display(ostream&) const;
//Screen &display(ostream &os){do_display(os); return *this;}
//const Screen &display(ostream &os) const{do_display(os); return *this;}
private:
string contents;
pos cursor = 0, height = 0, width = 0;
/*void do_display(ostream &os) const{
for (int i=0;i<height; ++i){
for (int j=0;j < width; ++j){
os << contents[j + i*width];
}
os << '\n';
}
}*/
};
Screen &clear(Screen &scrn){
string temp(scrn.width*scrn.height, ' ');
scrn.contents = temp; //为什么scrn.contents = " "就不行??
return scrn;
}
constructor
构造函数:
//初始化
Sales_data::Sales_data(const string &s, unsigned cnt, double price):
bookNo(s), units_sold(cnt), revenue(cnt * price){}
//赋值
Sales_data::Sales_data(const string &s, unsigned cnt, double price){
bookNo = s; units_sold = cnt; revenue = cnt * price;
}
// 为构造函数定义默认值
class A
{
public:
A(int a = 8);
int b;
}
A::A(int c=9): b(c) // 错误❌ 默认值不一致, c=8 或 c
{}
初始化操作和赋值操作不同
- 如果成员是const或者引用,必须初始化
类的成员初始化顺序与构造函数初始值列表顺序无关,按照在类中的定义顺序进行
最好令构造函数初始值的顺序与成员声名的顺序保持一致。而且如果可能的话,尽量避免使用某些成员初始化其他成员。
默认实参和构造函数
委托构造函数
class Sales_data{
public:
Sales_data(std::string s, unsigned cnt, double price):
bookNo(s), units_sold(cnt), revenue(cnt*preice){}
Sales_data():Ssles_data("", 0, 0) {}
Sales_data(std::string s):Sales_data(s, 0, 0) {}
Sales_data(std::istream &is):Sales_data() {read(is, *this);}
};
隐式类类型转换
//只能一步
string s = "abc";
item.combine(s); // ✔
item.combine(Salse_data("abc")); // ✔
item.combine(string("abc")) ; // ✔
item.combine("abc"); // ✔
//以制自动隐式类类型转换
// explicit关键字只允许出现在类内的构造函数声名处
explicit Sales_data(const std::string &s):bookNo(s){}
item.combine(s); // ❌
Sales_data item1(null_book); // ✔直接初始化
Sales_data item2 = null_book; // ❌不能将explicit构造函数用于拷贝形式的初始化过程
//显式强制转换
聚合类
c中的结构体
缺点较为明显,不建议使用
struct Data{
int ival;
string s;
};
Data val1 = {0, "Anna"}; // 顺序必须一致,
字面值常量类
类的静态成员
声名静态成员,为类的成员,所有类的实例共享该成员
struct A{
static int i;
};
需要在类的外部定义,类在.h中声明,定义在.cpp中
int A::i = 0; //不能在main中
(有点像python中的类属性)
建议通过 A::i访问
注: 类的静态常量数据成员需要在类定义中初始化,如static const int i = 0;
class Account{
public:
void calculate() {amount += amount * interestRate;}
// static关键字只能出现在类内
// 静态函数没有this指针,静态函数只能使用静态成员
static double rate() {return interestRate;}
static void rate(double);
private:
std::string owner;
double amount;
static double interestRate;
static double initRate();
};
//使用类的静态成员
double Account::interestRate = 67;
double r;
r = Account::rate();
Account ac1, *ac2 = &ac1;
r = ac1.rate();
r = ac2->rate(); //
在类中定义(声名)的static需要在类外(不能在main中)初始化
int A::i = 0
在函数内定义的static默认初始化0,在main外定义的static默认初始化0