C++ Primer 学习笔记_50_类与数据抽象 --类作用域
类
--类作用域
引言:
每个类都定义了自己的新作用域与唯一的类型。即使两个类具有完全相同的成员列表,它们也是不同的类型。每个类的成员不同与任何其他类(或任何其他作用域)的成员。
class First { public: int memi; double memd; }; class Second { public: int memi; double memd; }; First obj1; Second obj2 = obj1; //Error
1、使用类的成员
在类作用域之外,成员只能通过对象或指针使用成员访问操作符.或->来访问。这些操作符左边的操作数分别是一个类对象或指向类对象的指针。
还有一些直接通过类使用作用域操作符(::)来访问。如:定义类型的成员,Screen::index,使用作用域操作符来访问。
2、作用域与成员定义
尽管成员是在类的定义体之外定义的,但成员定义就好像它们是在类的作用域中一样。
//一旦看到成员的完全限定名,就知道该类定义是在类作用域中 double Sales_item::avg_price() const { if (units_sold) { return revenue / units_sold; } return 0; }
3、形参表和函数体处于类作用域中
在定义于类外部的成员函数中,形参表和成员函数体都出现在成员名之后。这些都是在类作用域中定义,所以可以不用限定而引用其他成员。
char Screen::get(index r,index c) const { index row = r * width; return contents[row + c]; }
4、函数返回类型不一定在类作用域中
与形参类型相比,返回类型出现在成员名字前面。如果函数在类定义体之外定义,则用于返回类型的名字在类作用域之外。如果返回类型使用由类定义的类型,则必须使用完全限定名。
class Screen { public: typedef std::string::size_type index; index get_cursor() const; //... }; //由于index出现在函数名被处理之前,所以代码不在类作用域内。 inline Screen::index Screen::get_cursor() const { return cursor; }
//P381 习题2.16 //error: ‘index‘ does not name a type index Screen::get_cursor() const { return cursor; }
类作用域中的名字查找
一直以来,我们程序的名字查找:
1)首先,在使用该名字的块中查找名字的声明。只考虑在该项使用之前声明的名字。
2)如果在1)中找不到,则在包围的作用域中查找。
如果找不到任何声明,则出错。
类的定义实际上是在两个阶段中处理:
1)首先,编译器声明;
2)只有在所有成员出现之后,才编译他们的定义本身。
1、类成员声明的名字查找
按照以下方式确定在类成员的声明中用到的名字:
1)检查出现在名字使用之前的类成员的声明。
2)第一步查找不成功,则价差包含类定义的作用域中出现的声明以及出现在类定义之前的声明!
如:
typedef double Money; class Account { public: Money balance() { return bal; } private: Money bal; };
【谨记:】
必须是在类中先定义类型名字,才能将它们用作数据成员的类型,或者成员函数的返回类型或形参类型。
一旦一个名字被用作类型名,则该名字就不能被重复定义:
typedef double Money; class Account { public: Money balance() { return bal; } private: typedef long double Money; //Error Money bal; };
2、类成员定于中的名字查找
1)首先检查成员函数局部作用域中的声明。
2)如果在成员函数中找不到该名字的声明,则检查对所有类成员的声明。
3)如果在类中找不到该名字的声明,则检查在此成员函数定义之前的作用域中出现的声明。
3、类成员遵循常规的块作用域名字查找
在程序中尽量不要使用相同名字的形参和成员:
int height; class Screen { public: typedef int index; //由于在函数形参中定义了height, //所以此处的height形参会屏蔽名为height的成员 void dummy_fcn(index height) { cursor = width * height; } private: index cursor; index height,width; };
尽管类的成员被屏蔽了,但是仍然可以通过使用类名来限定成员名或显式使用this指针来使用:
void dummy_fcn(index height) { cursor = width * this -> height; //这两条语句作用相同 cursor = width * Screen::height; }
4、函数作用域之后,在类作用域中查找
如果想要使用height成员,最好的方式还是为形参定义一个不同的名字:
void dummy_fcn(index ht) { cursor = width * height; }
尽管height是先在dummy_fcn中使用,然后再声明,但是编译器还是确定这里用的是名为height的数据成员。
5、类作用域之后,在外围作用域中查找
尽管全局对象height在类中被数据成员height屏蔽了,但还可以通过全局作用域确定操作符来限定名字,仍然可使用它:
void dummy_fcn(index height) { cursor = width * ::height }
6、在文件中名字的出现初确定名字
当成员定义在类定义的外部时,名字查找的第3步不仅要考虑在Screen类定义之前的全局作用域中的声明,而且要考虑在成员函数定义之前出现的全局作用域声明:
class Screen { public: typedef int index; void setHeight(index); private: index height; }; Screen::index verify(Screen::index); void Screen::setHeight(index val) { /* *val由形参定义 *verify是全局函数 *height是类的数据成员 */ height = verify(val); }
//P385 习题12.17 //编译查看错误 class Screen { public: void setHeight(index); private: index height; typedef int index; };
//习题12.18 typedef string Type; Type initVal(); class Exercise { public: Type setVal(Type); private: Type val; //string val; }; Type Exercise::setVal(Type parm) { val = parm + initVal(); return val; }