类的定义和声明
- 创建一个对象的时候,编译器会自动使用一个构造函数来初始化对象,构造函数一般使用初始化列表来初始化对象成员
Sales_item():unit_sold(0),revenue(0,0){}
- 类内部定义的函数默认是inline
- const成员函数不能改变所操作对象的数据成员。const必须同时出现在声明和定义中
- 类背后蕴含的思想是抽象与封装
- 可以在类内部或是外部的函数上定义inline,都是合法的
- class LinkScreen是不完全型,不能定义该类型的对象。不完全型只能用于定义指向该类型的指针和引用(重要)
class LinkScreen{ Screen window; LinkScreen *next; LinkScreen *prev; };
隐含的this指针
- this:成员函数具有一个附加的隐含形参,即指向该类对象的一个指针。成员函数不能定义this形参,而是由编译器隐含地定义,static不含this指针
- 何时使用this:当我们需要将一个对象作为整体引用而不是引用对象的一个成员的时:例如: myScreen.move(4,0).set('#'),我们需要move函数返回的是整体的引用Screen& Screen::move(index r, index c){....return *this}
- 普通的非const成员函数中,this是一个指向类类型的const指针,可以改变this所指向的值,但不能改变this所保存到地址。const成员函数中,this类型是一个指向const类类型对象的const指针
- 基于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;} };
- (NEW)可变的数据成员:可变成员函数永远不能为const,甚至当它是const对象的成员也是如此的,关键词是mutable。
class Screen{ public: .... private: mutable size_t access_ctr; } void Screen::do_display(std::ostream & os)const { ++access_ctr;//尽管do_display是const,它可以增加可变成员 ....... }
类作用域
- 一些直接通过类使用作用域操作符(::)来访问。如定义类型的成员,如Screen::index,使用作用域操作符来访问。
- 形参表和函数体处于类作用域中(可以直接调用类中的函数和成员)
- 函数的返回类型不一定在类作用域中
inline Screen::index Screen::get_cursor() const { return cursor; }
- 类成员定义的名字查找按以下方式确定成员函数的函数体中用到的名字:a首先检查成员函数局部作用域中的声明 ;b上步不成,则检查所有类成员的声明; c上步不成,检查此成员函数定义之前的作用域中出现的声明
int height; //第三步 class Screen{ public: void abc(index height) //第一步 { cursor = width*height;} private: index cursor; index height; //第二步 }
- 全局作用域调用 :: height
构造函数
- 默认构造函数:未定义构造函数,那么编译器会自动为为该类型生成构造函数。在显示定义了构造函数时,编译器不会生成构造函数
- 如果类包含内置类型或复合成员类型的时候,不应该依赖于合成的默认构造函数,它应该自己定义构造函数来初始化这些成员
- 如果定义了其它构造函数,最好提供一个默认的构造函数,给成员提供初始值应该指出该对象时" 空"
- 合成的默认构造函数对定义在全局作用域的类对象才有效。
- 构造函数不能声明为const
- 内置或复合类型的成员初始值依赖于对象的作用域:局部对象这些成员不初始化,全局作用域中被初始化为0
- 没有默认构造函数的类类型成员,以及const或引用类型的成员,不管是哪种类型,都必须在构造函数初始化列表中进行初始化。
- 构造函数初始化列表仅指定用于初始化成员的值,并不指定这些初始化执行的次序。(NEW)
- (有用)默认实参与构造函数
Sales_item(const std::string &book = " "):isbn(book),uints sold(0),revenue(0.0){}
- 容易犯的错误:Sales_item obj() 编译没有问题,这个其实是一个函数,正确的方式是:Sales_item obj 使用默认构造函数定义这个对象,也可以Sales_item myobj = Sales_item()
- NEW:可以用单个实参来调用的构造函数定义了从形参类型到该类类型的隐式转换,但是这个行为非常不好,下面的例子定义了从string到sales_item的隐式转换
class Sales_item{ public: Sales_item(const string &book = ""):isbn(book),uints_sole(0){} Sales_item(std::istream &is); }
- 为了避免由构造函数引起的可能的隐式转换,有关键词explict,只能用于类内部的构造函数的声明上
- 类成员的显示初始化,但缺点很大
struct Data{ int ival; char *ptr; } Data val = {0,0}
友元(以前忘的差不多了)
- 某些情况下,允许特定的非成员函数访问一个类的私有成员,同时阻止一般的访问,例如:输入输出操作符
- 友元friend可以在类中任意地方,由于它不是类成员,所以它们不受其声明出现的访问控制的影响
- 友元函数可以在类的内部定义,但是它的作用域扩展到包围该类定义的作用域(书中还有些友元声明与友元定义的相互依赖关系)
static类成员
- static数据成员独立于该类的任意对象而存在。每个static数据成员是与类关联的对象,而不是与该类的对象相关联
- static成员函数没有this形参,它可以直接访问所属的类的static成员,但不能直接使用非static成员,既可以在类内部定义,也可以在类外部定义。
- static成员函数不能声明为虚函数(15章巩固虚函数)
- static数据成员必须在类定义体外部定义。static不是通过构造函数初始化,而是在定义时进行初始化。
double Account::interestRate = initRate()
- 其中一个可以在类中定义初始化的例外是:const static:常量表达式,但是该数据成员仍然必须在定义体之外进行定义
- static成员不是类对象的组成部分
static数据成员还可以作为默认实参class Bar{ public: //.... private: static Bar mem1; //ok Bar *mem2; //ok Bar mem3; //error };
- 定义中就不需要再声明static,区别于const
- static成员的优点:
- static的名字是在类的作用域中,避免与其它类的成员或全局变量名字冲突
- 实施封装。static可以是私有成员,而全局对象不可以
- 可以看出static成员与特定的类相关联,了解程序员的意图