原创
【博客大赛】《C++ Primer》学习笔记(24)类
7.4 Class Scope ---------------------------------------------------------------- void Window_mgr::clear(ScreenIndex i) { Screen &s = screens; s.contents = string(s.height * s.width, ' '); } 此函数中的Window_mgr,说明这个函数都在它的scope里。 如果函数的返回值要使用Window_mgr中的类型的话,必须加上Window_mgr的specify。 比如: Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen &s); 编译器只有在找到所以类的成员声明之后,才会去处理成员函数的定义。 编译器会现在class里面找,然后在enclosing scope里找,再找不到就会报错。 在类的内部和外部,变量可以重名,但是typedef的定义不能够重名,否则就会报错。 编译器如何处理成员函数内部的名称? • 在成员函数内部寻找,且仅仅在使用这个名称之前的代码。 • 在类里面找。 • 在这个类前面的scope中找。 如果我们想使用类成员的名称,而不是成员函数里的本地名称,可以通过this或者类名访问: this->height; Screen::height; 如果我们想使用类外面的全局名称,就使用"::"这个符号,它意味着全局名称: ::height 7.5 Constructors Revisited ---------------------------------------------------------------- 如果在定义一个变量时,是先定义再赋值,那么这个变量会先被赋为默认值, 因此不如在初始化的时候顺便把值给初始化了: string foo = "Hello World!"; 构造函数也是如此,与其在内部赋值,不如在它的参数列表里面指定其成员的初始值。 const类型的变量/引用/指针,以及没有默认构造函数的类,必须在参数列表里被初始化。 在构造函数的参数列表里,成员初始化的顺便并不是列表的顺序,而是它们在类中的存储顺序。 因此,最好按照类中的存储顺序来初始化成员变量,并且不要使用成员变量来初始化另一个成员变量。 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); } 第一个函数是默认的构造函数,因为它提供的参数列表和默认的参数列表并没有区别。 我们也可以使用空列表来调用它。 ---------------- Delegating Constructors 使用其他构造函数来形成自己的构造函数: 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);} 此时,被delegating的构造函数的参数列表及函数体都会被执行,它们执行完了之后, delegating函数的函数体会接着执行。 ---------------- The Role of the Default Constructor 什么时候会用到默认的构造函数? • 在block scope中定义nonstatic变量,且没有初始化它。 • 类包含有synthesize default constructor的成员类。 • 当构造函数没有显式的初始化每一个成员变量时。 • 当没有提供完整的数组初始化个数时。 • 当定义一个本地的static对象且没有初始化时。 • 当初始化类似vector的变量时,T()使用了类作为其成员。 ---------------- Implicit Class-Type Conversions 不仅仅是build-in类型可以进行转换,class类型也可以。 单参数的构造函数,就代表了一个类型转换,因此这类函数也称为converting constructors。 但要注意的是,转换函数只能接受一次转换,如果构造函数接收string作为参数,那就不能使用: combine("9-999-99999"); 如果在构造函数前加上explicit关键字,意味着我们不能使用它来行类型的隐式转换。 explicit只需要用在单参数的converting constructors上; explicit只能用在类的内部,不能在类的外部使用; explicit只能用在直接的initialization上: Sales_data item1(null_book); // OK Sales_data item1 = null_book; // ERROR ---------------- Aggregate Classes 我们可以直接访问它的成员: struct Data { int ival; string s; }; Data val1 = {0, "Anna"}; 这种直接访问类成员的方式看其来很简单,实际上有很多缺点: • 所有的成员变量必须是public。 • 类的使用者必须负责进行冗长且乏味的赋值操作。 • 一旦类修改,所有的赋值操作都要修改。 ---------------- Literal Classes 一个aggregate class,如果它的成员变量都是literal类型的,就可以称它literal class。 一个非aggregate class,满足如下要求的也是literal class: • 所有的成员都是literal类型; • 至少有一个constexpr构造函数; • in-class的初始化必须是constant表达式,或者成员类有constexpr构造函数; • 必须使用默认的析构函数。 7.6 Class Members ---------------------------------------------------------------- static关键字,使得类的成员不再属于某个单独的类对象,而是属于整个类。 被static修饰的成员,可以是const/引用/数组/clas或其他。 被static修饰的成员,可以是private或者public。 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(); }; 每个Account个体仅仅包含owner和amount两个成员,其他被static修饰的都是共用的。 被static修饰的函数,不会有this指针。 被static修饰的函数,也不会是const的。 我们可以通过类的类型名直接访问static变量或函数: double r; r = Accout::rate(); 当然我们仍然可以通过具体的对象来访问它们。 成员函数可以直接访问被static修饰的成员变量。 在类的外部定义static函数时,不需要使用static修饰,在类的内部做就可以了。 类的构造函数不会去处理static成员变量,同理我们不能在类的内部去处理static变量。 static变量必须在类的外部进行定义和处理,就像是全局变量一样。 为了保证static成员变量只被定义一次,我们通常将它们放置在对应的源文件中。 一般来说,static成员变量不能在类的内部进行初始化,但也有例外的情况: 如果static成员是const integral类型,就可以在内部进行。 如果static成员只用在了编译器可直接使用它的值的地方,就无需在外面再定义,否则就需要。 如果static成员进行了类内部的初始化,外部的定义就不能再赋值了。 static成员定义在了类外面,因此它的数据类型可以是这个类本身。 static成员可以作为构造函数的参数。
用户593939 2016-3-28 18:03