C++Primer #7 类
类的定义
简单的来说类就是数据和它的操作的一种封装,内部提供接口函数
类的成员函数的声明必须在类的内部,而它的定义则既可以放在类的内部也可以放在类的外部。(一般在类内进行声明,类外实现函数定义)
定义在类内部的函数是隐式的inline函数(内联函数)。
构造函数
功能:初始化类对象的数据成员。无论何时只要类的对象被创建,就会执行构造函数。
特点:构造函数的名字和类的名字相同。类可以包含有多个构造函数(类似重载函数)。不同于其他成员函数,构造函数不能被声明为const,且没有返回类型。
默认构造函数:无需任何实参,执行默认初始化。
合成默认构造函数:只有当类没有声明任何构造函数时,编译器才会自动隐式地定义一个默认构造函数。
class Sales_data { public: Sales_data() = default; Sales_data(std::string s):bookNo(s), units_sold(0), revenue(0.0) { } Sales_data(std::string s, unsigned n, double p): bookNo(s), units_sold(n), revenue(p) { } private: std::string bookNo ; //编号 unsigned units_sold = 0; //销售数量 double revenue = 0; //总销售额 }; int main() { Sales_data item1; Sales_data item2("wangweihao"); Sales_data item3("wangweihao", 10, 10000); print(cout, item1) << endl; print(cout, item2) << endl; print(cout, item3) << endl; return 0; }
情况1 去掉 Sales_data( ) = default;
报错:没有定义默认构造函数,当我们自己定义了其他任何一种构造函数时,编译器就不会帮我们合成默认构造函数。这时需要加上Sales_data( ) = default; 定义默认构造函数。
函数成员初始化的顺序:与它们在类定义中的出现顺序一致。tips:最好令构造函数初始值顺序与成员声明的顺序保持一致,尽可能避免使用某些成员初始化其他成员,而是使用传入的变量。
举例:
class X{ int i; int j; public: // 错误:未定义的,i在j之前被初始化 X(int val):j(val),i(j){} };
类内定义构造函数:
Sales_data(const std::string& s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(n * p){}
先执行初始值列表 bookNo(s), units_sold(n), revenue(n * p) , 再执行{} 内函数体的内容。
委托构造函数(C++11)
一个委托构造函数使用它所属类的其他构造函数来执行它的初始化过程。加入被委托的构造函数函数体有代码的话,先执行完代码,再执行委托者的函数体。
class Sales_data { public: Sales_data(const std::string& s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(n * p){} // 其余构造函数全都委托给另一个构造函数 Sales_data() : Sales_data("", 0, 0.0f){} Sales_data(const std::string& s) : Sales_data(s, 0, 0.0f){} Sales_data(std::istream &is): Sales_data() {read(is,*this};} }
构造函数在数组中的使用
指针如果没有指向确定值,就没有生成对应的对象,也就没有调用构造函数。
new返回的是地址,所以前两个有生成对应的对象,而pArray2这个元素生成并不会导致任何对象的生成,所以这条语句, 只是生成了2个对象.
引入this
- 成员函数通过一个名为this的额外的隐式参数来访问调用它的那个对象。
- 任何对类成员的访问都被看作this的隐式引用。this是一个常量指针,不允许改变this中保存的地址
const 常量成员函数:C++允许把const关键字写在函数的参数列表后面,表示this是一个指向常量对象的指针。一个const成员函数如果以引用的形式返回*this,那么它的返回类型将是常量引用。
访问与封装
- 定义在public说明符之后的成员在整个程序内可被访问,public成员定义类的接口。
- 定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问。(隐藏了类的实现细节)
友元
类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数称为它的友元(friend)。如果想把一个函数作为友元,只需要添加一条以friend关键字开头的函数声明即可。
-
重载函数作为友元,尽管名字相同,但是他们依然是不同的函数。要分别对每一个函数进行声明
-
就算在内部定义友元函数,我们也应该在外部声明它使得它可见。
class Sales_data { friend std::istream& read(std::istream& is, Sales_data& item); friend std::ostream& print(std::ostream& os, const Sales_data& item); friend Sales_data add(const Sales_data& lhs, const Sales_data& rhs); }
- 当把一个成员函数声明为友元时,我们必须明确指出该成员函数属于哪个类
class Screen { friend void Window_mgr::clear(ScreenIndex); }
访问类的成员
类的作用域
名字查找
-
首先,在名字所在的块中寻找其声明语句,只考虑在名字使用之前出现的声明
-
如果没找到,继续查找外层作用域
-
如果最终没有找到匹配的报错
类的声明
class Screen; // Screen 类的声明
只声明类而暂时不定义它。称为前向声明,对于类而言,在它声明之后、定义之前是一个不完全类型。
应用场景:
- 定义指向这种类型的指针或引用
- 声明(但不能定义)以不完全类型作为参数/ 返回类型的函数
Tips:因为只有当类全部完成后类才算被定义,所以一个类的成员不能是该类自己。 然而,一个类的名字出现后,它被认为是声明 过了,因此类允许包含指向自身类型的引用或指针。
ex:定义一对类X和Y,其中X包含一个指向Y的指针,而Y包含一个类型为X的对象。
class Y; class X{ Y* y = nullptr; } class Y{ X x; }
类的静态成员
有的时候类需要它的一些成员与类本身直接相关,而不是与类的各个对象保持关联。如:一个银行账户类需要一个数据成员来表示当前的基准利率。
- 静态成员可以是不完全类型。
- 一个静态数据成员只能定义一次
- sizeof 运算符不会计算静态成员变量
-
class CMyclass{ int n; static int s; }
则 sizeof(CMyclass) = 4;
- 注:在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数。
声明静态成员
- 静态成员变量在类内声明,且必须带static关键字;在类外初始化,且不能带static关键字
- 静态成员函数在类内声明,且必须带static关键字;在类外实现,且不能带static关键字
class Account{ public: void calculate(){amout += amount* interestRate;} static double rate(){ return interestRate} static void rate(double); private: std::string owner; double amount; static double interestRate; static double initRate(); }
使用类的静态成员
通过作用域运算符::直接访问静态成员
double r; r = Account::rate();
定义静态成员
void Account::rate(double newRate) // 不能带static(重复) { interestRate = newRate; }
静态成员的类内初始化
通常情况下,类的静态成员不应该在类内初始化。然而,可以为静态成员提供const整数类型的类内初始值。不过要求静态成员必须是字面值常量类型的constexpr。初始值必须是常量表达式。
ex.找出下面的静态数据成员的声明和定义错误
// exmaple.h class Example{ public: static double rate = 6.5; // error: rate should be a constant expression. static const int vecSize = 20; static vector<double> vec(vecSize);//error: we may not specify an in-class initializer inside parentheses. }; // example.c #include "example.h" double Example::rate; vector<double> Example::vec;
Fixed:
// 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);
静态成员实例
考虑一个需要随时知道矩形总数和总面积的图形处理程序。可以用全局变量来记录总数和总面积,用静态成员将这两个变量封装进类中,就更容易理解和维护。