C++primer-learning-notes-chapter7

309 阅读9分钟

Sales_data类

#include <string>
#include <iostream>

class Sales_data {   //class和struct区别是第一个访问说明符之前的成员是public还是private

friend std::ostream& print(std::ostream&, const Sales_data&); //友元声明,可访问私有成员
friend std::istream& read(std::istream&, Sales_data&);

public:
	Sales_data() = default; //构造函数
	Sales_data(const std::string& s) :bookNo(s) {}
	Sales_data(const std::string& s, unsigned n, double p) :bookNo(s), units_sold(n), revenue(p* n) {}
	Sales_data(std::istream&);

	std::string isbn() const { return bookNo; }
	Sales_data& combine(const Sales_data&);

private:
	double avg_price() const;
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0.0;
};

Sales_data add(const Sales_data&, const Sales_data&);
std::ostream& print(std::ostream&, const Sales_data&);
std::istream& read(std::istream&, Sales_data&);

Sales_data& Sales_data::combine(const Sales_data& rhs)
{
	units_sold += rhs.units_sold;
	revenue += rhs.revenue;
	return *this;
}

double Sales_data::avg_price() const
{
	if (units_sold)
		return revenue / units_sold;
	else
		return 0;
}

std::istream& read(std::istream& is, Sales_data& item) {
	double price = 0;
	is >> item.bookNo >> item.units_sold >> price;
	item.revenue = price * item.units_sold;
	return is;
}

std::ostream& print(std::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& lhs, const Sales_data& rhs) {
	Sales_data sum = lhs;
	sum.combine(rhs);
	return sum;
}

Sales_data::Sales_data(std::istream& is) {
	read(is, *this);
}

类的其他特性

class Screen{
public:
    typedef std::string::size_type pos; //using pos = std::string::size_type;
    Screen() = default;
    Screen(pos ht,pos wd,char c):height(ht),width(wd),contents(ht*wd,c){}
    char get() const{return contents[cursor];} //隐式内联(定义在类内部的成员函数)
    inline char get(pos ht,pos wd) const; //显式内联
    //重载成员函数
    Screen& move(pos r,pos c);
private:
    pos cursor = 0;
    pos height = 0,width = 0;
    std::string contents;
};

inline char get(pos ht,pos wd) const{
    pos row = r * width;
    return contents[row + c];
}

inline Screen& move(pos r,pos c){   //可同时在定义和声明的地方说明inline,最好只在类外部定义地方说明inline,容易理解
    pos row = r * width;
    cursor = row + c;
    return *this;
}

可变数据成员

class Screen{
public:
    void some_member() const;
private:
    mutable size_t access_ctr;  //即使在一个const对象内也能被修改
};

void Screen::some_member() const{
    ++access_ctr;
}
//some_member是一个const成员函数,它仍能够改变access_ctr的值。

类数据成员的初始值

class Window_mgr{
private:
    std::vector<Screen> screens{Screen(24,80,' ')};
};

返回*this的成员函数

class Screen{
public:
    Screen& set(char);
    Screen& set(pos,pos,char);
};

inline Screen& Screen::set(char c){
    contents[cursor] = c;
    return *this;
}

inline Screen& Screen::set(pos r,pos col,char ch){
    contents[r*width+col] = ch;
    return *this;
}

//若定义的返回类型不是引用,则返回值是*this的副本,调用函数只能改变临时副本。

//display() 返回类型是const Screen&
Screen myScreen;
myScreen.display(cout).set('#');//编译出错,无权set一个常量对象。

基于const的重载

class Screen{
public:
    Screen& display(std::ostream& os){do_display(os);return *this;}
    const Screen& display(std::ostream& os){do_display(os);return *this;
private:
    void do_display(std::ostream& os) const{os<<contents;}
    }
};

Screen myScreen(5,3);
const Screen blank(5,3);
myScreen.set('#').display(cout); //调用非常量版本
blank.display(cout); //调用常量版本

类类型

  • 两个类,即使他们成员完全一样,这两个类也是两个不同的类型。
  • 允许类包含指向它自身类型的引用或指针。
class Link_screen{
    Screen window;
    Link_screen *next;
    Link_screen *prev;
};

深入友元

  • 类可把其他类定义成友元,也可把其他类的成员函数定义成友元。
  • 友元函数能定义在类的内部,这样的函数是隐式内联的。
  • 友元不具有传递性
class Screen{
    friend class Window_mgr;
    //也可令成员函数作为友元
    //此时window_mgr::clear必须在Screen类之前被声明
    //friend void Window_mgr::clear(ScreenIndex);
};

class Window_mgr{
public:
    using ScreenIndex = std::vector<Screen>::size_type;
    void clear(ScreenIndex);
private:
    std::vector<Screen> screens{Screen(24,80,' ')};
};

void Window_mgr::clear(ScreenIndex i){
    Screen& s = screens[i];
    s.contents = string(s.height * s.width,' ');
}

类的作用域

class Window_mgr{
public:
    ScreenIndex addScreen(const Screen&);
};

Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen& s){
   screens.push_back(s);
   return screen.size()-1;
}

名字查找与类的作用域

typedef double Money;
string bal;
class Account{
public:
    Money balance(){return bal;}//声明时,寻找类中函数声明前Money类型的定义,没找到的话
    //接着在外层作用域中查找,找到了typedef double Money.该类型被用作balance函数的返回类型
    //和数据成员bal的类型
    //balance函数体是在整个类可见后才被处理,因此,该函数返回的是名为bal的成员,而非外层
    //作用域的string对象。
private:
    Money bal;
};


typedef double Money;
class Account{
public:
    Money balance(){return bal;}
private:
    typedef double Money;  //错误,不能重新定义Money
    Money bal;
};

成员定义中的普通块作用域的名字查找

int height;
class Screen{
public:
    typedef std::string::size_type pos;
    void dummy_fcn(pos height){
        cursor = width * height;
    }
private:
    pos cursor = 0;
    pos height = 0,width = 0;
};

//当编译器处理dummy_fcn中的乘法表达式时,它首先在函数作用域内查找表达式中用
//到的名字,函数的参数位于函数作用域内,因此dummy_fcn函数体内用到的名字height指的是参数声明。

//改进,但不建议
void Screen::dummy_fcn(pos height){
    cursor = width * this->height;
    //或者 cursor = width * Screen::height;
    //两者指的都是成员height
}

//建议写法
void Screen::dummy_fcn(pos ht){
    cursor = width * height; //成员height
}

//不建议的写法,外层height被隐藏,但仍可访问
void Screen::dummy_fcn(pos height){
    cursor = width * ::height;
}

构造函数再探

构造函数初始值列表

//Sales_data构造函数的一种写法,合法但较草率:没有使用构造函数的初始值
Sales_data::Sales_data(const string& s,unsigned cnt,double price){
    bookNo = s;
    units_sold = cnt;
    revenue = cnt * price;
}
//这个版本对数据成员进行了赋值操作。有局限性

class ConstRef{
public:
    ConstRef(int ii);
private:
    int i;
    const int ci;
    int &ri;
};
//由于是常量对象和引用,上面ci和ri都必须初始化
ConstRef::ConstRef(int ii){
    i = ii;
    ci = ii;  //错误:不能给const赋值
    ri = i;   //错误:ri没被初始化就赋值
}
//这时赋值初始化会出问题

//正确写法
ConstRef::ConstRef(int ii):i(ii),ci(ii),ri(i){}
//在很多类中,初始化和赋值的区别事关底层效率问题:前者直接初始化数据成员,后者则先初始化再赋值

成员初始化的顺序

  • 最好令构造函数初始值的顺序与成员声明的顺序保持一致,且尽量避免使用某些成员初始化其他成员
  • 成员初始化的顺序与它们在类定义中的出现顺序一致。

默认实参和构造函数

class Sales_data{
public:
    Sales_data(std::string s = ""):bookNo(s){}
    Sales_data(std::string s,unsigned cnt,double rev):bookNo(s),units_sold(cnt),revenue(rev*cnt){}
    Sales_data(std::istream& is){read(is,*this);}
};
//上面第一个构造函数,不提供实参也能调用上述构造函数,所以该构造函数实际上为我们的类提供了默认构造函数

委托构造函数

class Sales_data{
public:
    //非委托构造函数使用对应的实参初始化成员
    Sales_data(std::string s,unsigned cnt,double price):bookNo(s),units_sold(cnt),revenue(cnt*price){}
    //其余构造函数全部委托给另一个构造函数
    Sales_data():Sales_data("",0,0){}
    Sales_data(std::string s):Sales_data(s,0,0){}
    Sales_data(std::istream& is):Sales_data(){read(is,*this);}
};
//受委托的构造函数的初始值列表和函数体被依次执行,若函数体有代码,先执行这些代码,然后控制权才会交给委托者的函数体。

隐式的类型转换

  • 如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制。这种构造函数又叫转换构造函数(converting constructor)。
string null_book = "99999";
item.combine(null_book);
//用string对象调用Sales_data的combine成员,合法,自动创建了一个Sales_data对象,新生成
//的对象被传递给combine。

//只允许一步类类型转换
item.combine("9999");//错误

item.combine(string("99999"));//正确
item.combine(Sales_data("9999"));//正确

item.combine(cin);//隐式把cin转换成Sales_data,这个转换执行了接受一个istream的Sales_data
//构造函数。该构造函数通过读取标准输入创建了一个临时的Sales_data对象,随后
//将得到的对象传递给combine。

抑制构造函数定义的隐式转换

class Sales_data{
public:
    Sales_data() = default;
    Sales_data(const string& s,unsigned n,double p):bookNo(s),units_sold(cnt),revenue(p*n){}
    
    explicit Sales_data(const std::string& s):bookNo(s){}
    explicit Sales_data(std::istream&);
};
//将构造函数声明为explicit阻止隐式转换
item.combine(null_book);
item.combine(cin);//这两个都将无法通过编译
//explicit只对一个实参的构造函数有效。需要多个实参的构造函数不能用于执行隐式转换,
//也就无需指定为explicit,类内声明,类外不应重复。

explicit构造函数只能用于直接初始化

Sales_data item1(null_book);//正确
Sales_data item2 = null_book;//错误

为转换显式地使用构造函数

  • 尽管编译器不会将explicit的构造函数用于隐式转换过程,但是我们可以使用这样的构造函数显式地强制进行转换
item.combine(Sales_data(null_book));
item.combine(static_cast<Sales_data>(cin));
  • 标准库中接受一个单参数的const char*的string构造函数不是explicit的。接受一个容量参数的vector构造函数是explicit的。

聚合类

struct Data{
    int ival;
    string s;
};

Data val1 = {0,"Anan"};
  • 所有成员都是public的。没有定义任何构造函数。没有类内初始值。没有基类,也没有virtual函数。 可以使用一个花括号括起来的成员初始值列表,初始值的顺序必须和声明的顺序一致。

字面值常量类

  • constexpr函数的参数和返回值必须是字面值。
  • 字面值类型:除了算术类型、引用和指针外,某些类也是字面值类型。
  • 数据成员都是字面值类型的聚合类是字面值常量类。
  • 如果不是聚合类,则必须满足下面所有条件:
  • 数据成员都必须是字面值类型。
  • 类必须至少含有一个constexpr构造函数。
  • 如果一个数据成员含有类内部初始值,则内置类型成员的初始值必须是一条常量表达式;或者如果成员属于某种类类型,则初始值必须使用成员自己的constexpr构造函数。
  • 类必须使用析构函数的默认定义,该成员负责销毁类的对象。

类的静态成员

class Accout{
public:
    void calculate(){amount += amount * interestRate;}
    static double rate(){return interestRate;}
    static void rate(double);
private:
    std::string owner;
    double amount;
    static double interestRate;
    static double initRate();
};
  • 类的静态成员存在于对象之外,对象中不包含任何与静态数据成员有关的数据。不能用this指针,不能声明成const。

使用类的静态成员

double r;
r = Account::rate();

Account ac1;
Account *ac2 = &ac1;
r = ac1.rate();
r = ac2->rate();
  • 成员函数不用通过作用域运算符就能直接使用静态成员

定义静态成员

  • 既可以在类内部也可在外部定义静态成员,外部定义时不能重复static关键字
void Account::rate(double newRate){
    interestRate = newRate;
}

//定义并初始化一个静态成员
double Account::interestRate = initRate();

//若非要在类内部定义,则要求必须是字面值常量类型的constexpr。
class Account{
private:
    static constexpr int period = 30;
    double daily_tbl[period];
};

静态成员能用于某些场景,普通成员不能

class Bar{
public:
    //...
private:
    static Bar mem1;  //静态成员可以是不完全类型
    Bar *mem2; //指针成员可以是不完全类型
    Bar mem3; //数据成员必须是完全类型
};

//可以使用静态成员作为默认实参
class Screen{
public:
    Screen& clear(char = bkground);
private:
    static const char bkground;
};
  • 类的静态成员类内初始化
// example.h
class Example {
public:
    static constexpr double rate = 6.5;
    static const int vecSize = 20;
    static vector<double> vec;
};

// example.C
#include "example.h"
constexpr double Example::rate;
vector<double> Example::vec(Example::vecSize);