下城区网站建设价格查询,网站前台数据库,python 类似wordpress,网站建设规划书300字参考资料#xff1a;
《C Primer》第5版《C Primer 习题集》第5版
7.4 类的作用域#xff08;P253#xff09;
每个类都有自己的作用域#xff0c;在类的作用域之外#xff0c;普通的数据和函数成员只能由对象、引用或指针使用成员访问运算符访问#xff0c;类型成员则…参考资料
《C Primer》第5版《C Primer 习题集》第5版
7.4 类的作用域P253
每个类都有自己的作用域在类的作用域之外普通的数据和函数成员只能由对象、引用或指针使用成员访问运算符访问类型成员则通过作用域运算符访问。
作用域和定义在类外部的成员
对于定义在类外部的函数成员而言一旦遇到类名定义的剩余部分就是在类的作用域之内了这就是为什么参数列表和函数体可以直接使用类的其他成员
void Window_mgr::clear(ScreenIndex i){ // ScreanIndex是类Window_mgr中的类型成员Screen s screens[i];s.contents string(s.height*s.width, );
}需要注意的是由于返回类型出现在类名之前所以它位于类的作用域之外所以如果我们的返回类型是 ScreenIndex 就必须写成 Window_mgr::ScreenIndex 。
7.4.1 名字查找与类的作用域P254
一般的**名字查找name lookup**过程比较直接
在名字所在块中寻找声明语句。如果没找到继续查找外层作用域。如果最终没有找到则报错。
这里名字查找只考虑出现名字使用之前出现的声明
由于类的定义分两步处理首先编译成员的声明然后编译函数体。所以成员函数体可以使用类中的任何名字而无需考虑这个名字是否在前面出现过。
用于类成员声明的名字查找
需要注意的是只有成员函数体具有特殊性成员函数的参数列表和返回类型仍然遵循一般名字查找的规则
using Money double;
string bal;
class Account{
public:Money balance() { return bal; } // 这里bal的类型是MoneyMoney是double的别名
private:Money bal;
};类型名要特殊处理
如果类成员使用了某个名字这个名字代表某种类型则类不能再重新定义这个名字。
using Money double;
class Account{
public:Money balance() { return bal; }using Money double; // 错误Money不能重新定义即使与原定义一致也不行
};需要说明是某些编译器会忽略这个约定比如 visual studio 。
成员定义中的普通块作用域的名字查找
成员函数体内的名字按照如下方式查找
在函数内查找该名字只有在使用之前出现的声明才被考虑。如果在函数内没找到则在类内查找此时无需考虑先后顺序。如果类内没找到则在成员函数定义之前的作用域内寻找。
// 最好不要隐藏同名成员这里仅作演示
pos height;
void Screen::dummy_fcn(pos height){cursor width * height; // 参数中的heightcursor width * this-height; // 类中的heightcursor width * Screen::height; // 类中的heightcursor width * ::height; // 全局变量height
}在文件中名字的出现处对其进行解析
class X{void func(int);
};
int y;
void X::func(int i){i y; // y的声明已经出现过了所以可以正常使用
}7.5 构造函数再探P257
7.5.1 构造函数初始值列表P258
如果没有在构造函数初始值列表中显式初始化成员则该成员将在函数体之前执行默认初始化
Sales_data::Sales_data(const string s, unsigned cnt, double price){bookNo s;units_sold cnt;revenue cnt * price;
}上面这段代码相当于先默认初始化成员然后再给成员重新赋值。
构造函数的初始值有时必不可少
如果成员是 const 、引用或某种没有定义默认构造函数的类类型则必须将其初始化。
一旦构造函数体开始执行初始化就完成了。
成员初始化的顺序
在构造函数初始值列表中每个成员只能出现一次。构造函数初始值列表只说明用于初始化成员的值而没有限定初始化的顺序。成员的初始化顺与它们在类定义中的出现顺序一致
class X {int i;int j;
public:// 想用未定义的j初始化i再用val初始化jX(int val):j(val), i(j) { }
};默认实参和构造函数
class Sales_data {
public:Sales_data(string s ) :bookNo(s) { }
};如果一个构造函数为所有参数都提供了默认实参则它实质上也定义了默认构造函数。
7.5.2 委托构造函数P261
C11 新标准允许我们使用委托构造函数delegating constructor。委托构造函数使用本类中其他构造函数执行自己的初始化过程
class Sales_data {
public:Sales_data(string s, unsigned cnt, double price) :bookNo(s), units_sold(cnt), revenue(cnt *price) { }// 委托构造函数Sales_data() :Sales_data(, 0, 0) { }Sales_data(string s) :Sales_data(s, 0, 0) { }// 先委托默认构造函数然后默认构造函数在委托三参数构造函数// 执行完受委托的构造函数后再执行委托函数的函数体Sales_data(istream is) :Sales_data() { read(is, *this); }
};7.5.3 默认构造函数的作用P262
当类类型的对象被默认初始化或值初始化时自动执行默认构造函数。
使用默认构造函数
Sales_data obj1(); // 声明了一个函数
Sales_data obj2; // 使用默认构造函数7.5.4 隐式的类类型转换P263
如果某个类类型定义了能通过一个实参调用的构造函数则它实际上定了转换成此类类型的隐式转换机制这种构造函数称作转换构造函数converting constructor
class Sales_data {
public:// 转换构造函数Sales_data(string s) :bookNo(s), units_sold(0), revenue(0) { }
};
void func(Sales_data x) { ; }string null_book 9-999-99999-9;
func(null_book); // 正确可以理解为用这个参数隐式构造一个临时量。
只允许一步类类型转换
func(9-999-99999-9); // 错误需要先将const char*转换成string再由string转换成Sales_data类类型转换不是总有效
以上面的代码为例并不是所有的 string 都是我们需要的 bookNo 。
抑制构造函数定义的隐式转换
我们可以将构造函数声明为 explicit 来阻止隐式类型转换
class Sales_data {
public:// 阻止隐式类型转换explicit Sales_data(string s) :bookNo(s), units_sold(0), revenue(0) { }
};
void func(Sales_data x) { ; }string null_book 9-999-99999-9;
func(null_book); // 错误只能在类内声明构造函数时使用 explicit 不能在类外使用 explicit 。
explicit构造函数只能用于直接初始化
Sales_data item1(null_book); // 正确
Sales_data item2 null_book; // 错误为转换显式使用构造函数
func(static_castSales_data(null_book)); // 正确7.5.5 聚合类P266
当一个类满足如下条件时它是聚合类aggregate class
所有成员都是 public 。没有定义任何构造函数。没有类内初始值。没有基类也没有 virtual 函数。
聚合类允许用户直接访问其成员并且具有特殊的初始化形式
struct Data {int ival;string s;
};Data val1{ 0, hello };如上面的代码所示我们可以用一个花括号括起来的初始值列表来构造聚合类初始值的顺序必须与类中定义的顺序相同。如果初始值列表中的元素个数少于类的成员数量则靠后的成员被值初始化。
7.5.6 字面值常量类
数据成员都是字面值类型的聚合类是字面值常量类如果一个类不是聚合类但它符合下述要求则它也是字面值常量类
数据成员都是字面值类型。类必须至少有一个 constexpr 构造函数。如果一个数据成员有类内初始值这个初始值或是一条常量表达式或是调用数据成员自己的 constexpr 构造函数。类必须使用析构函数的默认定义。
constexpr构造函数
前面提到字面值常量类至少有一个 constexpr 构造函数。构造函数可以声明成 default 。否则 constexpr 必须既满足构造函数的要求不需要 return 语句又满足 constexpr 函数的要求唯一可执行语句就是返回语句。综合上述两点可知constexpr 构造函数体一般为空
class Debug {
public:constexpr Debug(bool b true) :hw(b), io(b), other(b) { }constexpr Debug(bool h, bool i, bool o):hw(h), io(i), other(o) { }constexpr bool any() { return hw || io || other; }
private:bool hw;bool io;bool other;
};constexpr 构造函数必须初始化所有数据成员有默认初始值的成员可以不显式初始化初始值或者使用constexpr 构造函数或者是一条常量表达式。
7.6 类的静态成员P268
有时候类需要一些成员与类本身直接相关而不是与类的各个对象保持关联。
声明静态成员
我们可以在成员的声明前加上关键字 static 使之与类关联在一起
class Account {
public:void calculate() { amount amount * interestRate; }static double rate() { return interestRate; }static void rate(double);
private:string owner;double amount;static double interestRate;static double initRate();
};类的静态成员存在于任何对象之外所以每个 Account 对象将包含两个数据成员 owner 、amount 。interestRate 对象只有一个并被所有对象共享。静态函数成员也不与任何对象绑定在一起所以不包含 this 指针也不能声明成 const 。
使用类的静态成员
可以使用作用域运算符直接访问静态成员
double r;
r Account::rate();尽管静态成员不属于类的某个对象但我们仍然可以通过类的对象、引用或指针来访问静态成员。
成员函数不通过作用域运算符也能直接使用静态成员
class Account {
public:void calculate() { amount amount * interestRate; }
private:static double interestRate;
}定义静态成员
在类外部定义静态成员时不能出现 static 关键字static 只能出现在类内部的声明语句。
由于静态数据成员不属于类的任何一个对象所以它们不是在创建类的对象时被定义的也不是由构造函数初始化的。
一般来说我们通常在任何函数外部定义和初始化静态成员
double Account::interestRate initRate();由于见到 Account 后就知道当前处在类的作用域所以后面调用 initRate() 不需要作用域运算符同函数成员在类外部定义一样interestRate 的定义也可以访问类中的私有成员。
静态成员的类内初始化
前面提到类的静态成员不应该在类内初始化。但我们可以为静态成员提供 const 整数初始值似乎没有这个要求但是要求静态成员必须是字面值常量类型 constexpr 且初始值必须是常量表达式。
class Account {
private:static constexpr int period 30;double daily_tbl[period];
};尽管一个常量静态数据成员已经在类内部初始化了我们通常还是要在类外部定义一下这个成员此时不能再指定初始值
constexpr int Account::period;静态成员能用于某些场景而普通成员不能
静态数据成员可以是不完全类型比如它本身所属的类类型
class X{
public:static X mem1; // 正确X mem2; // 错误
};此外我们可以使用静态成员作为函数成员的默认实参。