【C++ Primer Chapter 7 总结】类
1.类(class)是使用了数据抽象和封装技术的抽象数据类型。 接口和实现分开。
2.定义在类内的函数是隐式地(implicitly)内联函数。
3.成员函数通过隐式参数 this 指针访问调用它们(函数)的对象。当调用成员函数的时候,this被初始化为调用该函数的对象的地址。this是const pointer to non-const。
任何直接使用类成员都被认为是通过this的隐式地引用。
在参数列表后面的const表示this是指向const的指针。使用const的成员函数是const成员函数。
const对象,指向const对象的指针或者引用,只能调用const成员函数。 (intuition:const对象调用成员函数的时候,传递给函数的隐式参数classType* const this的实参是const对象本身,由于函数调用时,是实参类型向形参类型转换,所以如果实参是是const对象,则形参的this指针也只能指向const的,即:const classType* const this。即:该成员函数是const成员函数)
class Sales_data { ... std::string isbn() const {return bookNo;} // implicitly inline // std::string isbn() const {return this->bookNo;} // same, const说明this指向的是const对象 } Sales_data total; total.isbn(); // 编译器rewrite: Sales_data::isbn(&total); &total传递给this参数
4.编译器处理类分2步:先编译成员声明,然后编译成员函数体。因此成员函数体可以使用任何其他的类成员,无论是否在该成员函数前面声明。 此两步过程仅适用于成员函数体中使用的名称。 声明中使用的名称,包括用于返回类型的名称和参数列表中的类型,必须在使用之前声明。
5.构造函数。作用:初始化类对象的成员函数。没有返回类型。不能是const类型(因为初始化一定会改变对象数据成员)。对于const对象,在构造函数调用期间可以写入。
默认构造函数。a. 如果有默认类内初始值,用该值初始化成员,否则默认初始化成员。如果自定义了任何构造函数,系统将不再生成默认构造函数,也需自定义。
b. 当构造函数初始值列表中省略成员时,将使用与默认构造函数相同的过程隐式地对其进行初始化,即被初始化为类内初始值。
class Sales_data { Sales_data() = default; Sales_data(const std::string &s): bookNo(s) { } // b. same as: Sales_data(const std::string &s, unsigned n, double p): bookNo(s), units_sold(0), revenue(0.0) { } Sales_data(const std::string &s, unsigned n, double p): bookNo(s), units_sold(n), revenue(p*n) { } // 构造函数初始值列表,指定要创建对象的一个或多个数据成员的初始值 Sales_data(std::istream &); // other members as before std::string isbn() const { return bookNo; } Sales_data& combine(const Sales_data&); double avg_price() const; std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; // 类内初始值(in-class initializer) };
6.如果我们没有在构造函数初始值列表中显式初始化成员,则该成员将在构造函数主体开始执行之前进行默认初始化(使用类内初始化值)。
成员按照它们在类定义中出现的顺序进行初始化:首先初始化第一个成员,然后初始化下一个,依此类推。 初始化程序在构造函数初始化程序列表中出现的顺序不会更改初始化顺序。
class ConstRef { public: ConstRef(int ii); private: int i; const int ci; int &ri; // ri没有初始化值 }; ConstRef::ConstRef(int ii) { // 赋值不等于初始化! i = ii; // 赋值 ci = ii; // error: 不能对const变量赋值 ri = ii; // error: ri是引用不能赋值 } ConstRef::ConstRef(int a, int b, int c): i(a), ci(b), ri(c){} //使用初始化值列表初始化。 i被初始化为a,ci被初始化为b,ri被初始化为c
7.委托构造函数(delegating constructor)。 在委托的构造函数中,成员初始化列表只有一个条目,该条目是类本身的名称。
class Sales_data { public: // nondelegating constructor initializes members from corresponding arguments Sales_data(std::string s, unsigned cnt, double price): bookNo(s), units_sold(cnt), revenue(cnt*price) { } // remaining constructors all delegate to another constructor 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); } // other members as before };
8.转换构造函数(converting constrcutor)。 可以使用单个参数调用的构造函数定义了从构造函数的参数类型到类类型的隐式转换。编译器只会自动地执行一次隐式类型转换。
explicit关键字。阻止构造函数的隐式转换。显式关键字仅在类内部的构造函数声明上使用。类外定义不需要重复说明。
显式关键字仅在使用单个参数调用的构造函数上才有意义。 需要更多参数的构造函数不会用于执行隐式转换,因此无需将此类构造函数指定为显式。
显示构造函数只能用于直接的初始化和显示的类型转换。
class Sales_data { public: Sales_data(const std::string &s): bookNo(s) { } Sales_data& combine(const Sales_data&); ... }; Sales_data item; string book = "123-456-789"; item.combine(book); // covert string to Sales_data type item.combine("123-456-789"); // error: 2 converting. "123-456-789" is implicitly converted to temporary string, string is implicitly converted to Sales_data. item.combine(string("123-456-789")); // ok: "123-456-789" is explicitly converted to temporary string, string is implicitly converted to Sales_data. item.combine(Sales_data("123-456-789")); // ok: "123-456-789" is implicitly converted to temporary string, string is explicitly converted to Sales_data. class Sales_data { public: Sales_data() = default; Sales_data(const std::string &s, unsigned n, double p): bookNo(s), units_sold(n), revenue(p*n) { } explicit Sales_data(const std::string &s): bookNo(s) { } // 不能通过编译器隐式转换调用 explicit Sales_data(std::istream&); // 不能通过编译器隐式转换调用 Sales_data& combine(const Sales_data&); ... }; item.combine(book); // error: string constructor is explicit item.combbine(cin); // error: istream constructor is explicit Sales_data item1(book); // ok: 直接初始化 Sales_data item2 = book; // error
9.访问控制和封装。关键字:public和private。
struct和class的区别:struct默认成员public,class默认成员private。
访问类的非public的成员:在被访问的类内声明允许访问的类/函数为友元(friend)。友元不是类的成员,可以出现在类定义的任何地方(private/public不对其做约束)。
友元类型一般要声明2次:类内声明为友元(friend)1次,类外正常声明1次。
友元性质不可传递。A是B的友元,B是C的友元,不能推出:A是C的友元。
使成员函数成为友元需要仔细构造我们的程序,以适应声明和定义之间的相互依赖性。
class Sales_data { // friend declarations for nonmember Sales_data operations added friend Sales_data add(const Sales_data&, const Sales_data&); friend std::istream &read(std::istream&, Sales_data&); friend std::ostream &print(std::ostream&, const Sales_data&); friend class Product; // Product类的所有成员函数都是该类的友元 // other members and access specifiers as before public: Sales_data() = default; Sales_data(const std::string &s, unsigned n, double p): bookNo(s), units_sold(n), revenue(p*n) { } Sales_data(const std::string &s): bookNo(s) { } Sales_data(std::istream&); std::string isbn() const { return bookNo; } Sales_data &combine(const Sales_data&); private: std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; // declarations for nonmember parts of the Sales_data interface Sales_data add(const Sales_data&, const Sales_data&); std::istream &read(std::istream&, Sales_data&); std::ostream &print(std::ostream&, const Sales_data&); class Screen { // Window_mgr::clear must have been declared before class Screen,Window_mgr::clear cannot be defined before Sceen, cause it use the member of Screen friend void Window_mgr::clear(ScreenIndex); // ... rest of the Screen class };
10.在类定义的时候将函数定义为内联函数(inline)。
11.mutable数据成员永远不能是const,即使是const对象的成员。const成员函数可以修改mutable成员。
12.const成员函数将*this的引用作为返回值时,返回值类型需要设置为绑定到const的引用。因为const成员函数表示this指针是指向的是const类型。
13. 一看到类名,该类即被视为已声明(但尚未定义)。 因此,一个类可以拥有指向其自身类型的指针或引用的数据成员。
一旦看到了类名,定义的其余部分(包括参数列表和函数体)就在该类的范围内。
函数的返回类型通常出现在函数名称的前面。 当在类主体之外定义成员函数时,在返回类型中使用的任何名称都在类范围之外,因此需要声明作用域。
class Link_screen {
Screen window;
Link_screen *next;
Link_screen *prev;
};
class Window_mgr {
public:
ScreenIndex addScreen(const Screen&);
// other members as before
};
Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen &s)
{
screens.push_back(s);
return screens.size() - 1;
}
14.聚合类(aggregate class)。字面值类(literal class)。
15.类的静态成员。静态成员不属于任何对象,因此静态成员函数没有this指针。
直接使用作用域操作符访问静态成员。或者使用类类型的对象,引用,指针来访问静态成员。
static关键字仅在类内部的构造函数声明上使用。类外定义不需要重复说明。
由于静态数据成员不是类类型的单个对象的一部分,因此在创建类对象时不会定义它们。 因此,它们不会由类的构造函数初始化。
a. 一般在类主体外部定义和初始化每个静态数据成员。静态数据成员只能定义一次。
b. 我们可以为具有const整型的静态成员提供类内初始化值,并且必须为字面值类型的constexpr的静态成员提供初始化值。
c. 即使在类主体中初始化了const静态数据成员,通常也应在类外定义该成员。并且如果在类内提供了初始化值,则成员的定义不得指定初始值。
class Account { 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(); static constexpr int period = 30; // b. 类内初始化,period is a constant expression }; void Account::rate(double newRate){ interestRate = newRate; } double Account::interestRate = initRate(); // 类外不需要static说明 constexpr int Account::period; // c. 已经在类内提供初始值了 double r; Account ac1; Account *ac2 = &ac1; r = Account::rate(); r = ac1.rate(); r = ac2->rate(); // same