1.定义sale_data类
1.1.成员与非成员函数的声明与定义
成员函数的声明必须在类的内部,它的定义则既可以在类的内部也可以在类的外部。作为接口组成部分的非成员函数,例如 add、read和print等,它们的定义和声明都在类的外部。定义在类内部的函数是隐式的inline函数
1.2this指针及常量成员函数
1.2.1关于this
当我们用类的实例调用一个成员函数时,用请求该函数的对象地址初始化this。(this总是指向这个实例对象,因此this是一个常量指针)
在成员函数定义时,可以免去使用this->成员变量名:
double sales_data: :avg_price() const { if (units_sold) return revenue/units_sold; else return 0; }
1.2.2常量成员函数(this指针给加上const,常量对象为了不能改变其值只能调用常量成员函数)
//1.常量成员函数: std:: string isbn() const { return this->bookN; } //常量成员函数的意义: //常量对象,以及常量对象的引用或指针都只能调用常量成员函数。 //1.在常量成员函数中不能修改成员变量的值(静态成员变量除外); //2.也不能调用同类的 非 常量成员函数(静态成员函数除外) class Sample { public: void GetValue() const {} // 常量成员函数 void func(){} int m_value; }; void Sample::GetValue() const // 常量成员函数 { m_value = 0; // 出错 //1.在常量成员函数中不能修改成员变量的值(静态成员变量除外); func(); // 出错 //不能调用同类的 非 常量成员函数(静态成员函数除外) } int main() { const Sample obj; obj.m_value = 100; // 出错,常量对象不可以被修改 obj.func(); // 出错,常量对象上面不能执行 非 常量成员函数 obj.GetValue // OK,常量对象上可以执行常量成员函数 return 0; }
1.2.3类和成员函数的作用域
编译器首先编译的是成员声明,然后才是成员函数体,所以成员函数体不需要介意次序问题
※1.2.4定于返回this对象的函数
//返回值是引用类型,return是返回this的解引用 sales_data& Sales_data: : combine (const Sales_ata &rhs) { units_sold += rhs.units_sold;//把rhs的成员加到this对象的成员上revenue += rhs.revenue; revenue = rhs.revenue; return *this//返回调用该函数的对象 } total.combine(trans);//total的地址给绑定到隐式this参数上
2.定义类相关的非成员函数
2.1一般来说,如果非成员函数是类接口的组成部分,则这些函数的声明应该与类在同一个头文件内。
2.2构造函数(初始化类对象的数据成员)-------->定义了拷贝构造函数,没有默认构造函数的时候不能使用A a;定义一个对象
2.2.1
编译器创建的构造函数又被称为合成的默认构造函数( synthesized default constructor)。对于大多数类来说,这个合成的默认构造函数将按照如下规则初始化类的数据成员:如果存在类内的初始值(参见2.6.1节,第64页),用它来初始化成员。否则,默认初始化(参见2.2.1节,第40页)该成员。
只有当类没有声明任何构造函数时,编译器才会自动地生成默认构造函数。
如果类包含有内置类型或者复合类型(数字或者指针)的成员,则只有当这些成员全都被赋予了类内的初始值时(否则可能得到未定义的值),这个类才适合于使用合成的默认构造函数。
2.2.2 =default
Sales_data() = default; //其中,= default既可以和声明一起出现在类的内部,也可以作为定义出现在类的外部。 //和其他函数一样,如果= default在类的内部,则默认构造函数是内联的;如果它在类的外部,则该成员默认情况下不是内联的。
2.2.3构造函数初始值列表
Sales_data(const std::string &s): bookNo(s){ } Sales_data(const std::string &s,unsigned n,double p) : bookNo(s), units_sold(n) , revenue(p*n) { } //不能使用类内初始值,则所有构造函数都应该显式地初始化每个内置类型的成员。
3.访问控制与封装
3.1.struct与class的唯一区别:默认访问权限不同
在第一个访问说明符出现前,struct的所有成员是public,则class的是private
3.2友元(友元不是类的成员也不受它所在区域访问控制级别的约束。)
//友元的声明(类定义内) friend sales_data add (const Sales_data&,const Sales_data&); friend std: :istream &read(std: :istream&,sales_data&) ; friend std::ostream &print (std ::ostream&, const Sales_data&); //友元声明(类外) Sales data add (const Sales data&, const salesdata&);
4.类的其他特性
4.1内联函数
1.类内定义的函数------>隐私内联
2.最好在类外定义的时候说明内联
3.inline成员函数也应该与相应的类定义在同一个头文件中。
4.2可变数据成员(mutable)
一个可变数据成员( mutable data member)永远不会是const,即使它是const对象的成员。因此,一个const成员函数可以改变一个可变成员的值。
class Screen { public: void some_member() const; private: mutable size_t access_ctr;//即使在一个const对象内也能被修改//其他成员与之前的版本一致 }; void screen: :some_member ( ) const{ ++access_ctr;//保存一个计数值,用于记录成员函数被调用的次数 //该成员需要完成的其他工作 }
4.3返回*this的成员函数
const成员函数若以引用的方式返回*this,返回类型是常量引用
4.4友元再探
4.4.1友元类(不具有传递性---->window_mgr的友元不能访问screen)
class screen { //window mgr的成员可以访问Screen类的私有部分 friend class window_mgr; //Screen类的剩余部分 };
4.4.2成员函数做友元(必须明确指出该成员函数属于哪个类)
class screen { // window _mgr : :clear必须在Screen类之前被声明 friend void window_mgr::clear(screenIndex); // screen类的剩余部分 };
4.4.3函数重载与友元(需要对这组函数中的每一个分别进行friend声明)
※4.4.4友元作用域
struct x{ friend void f() { /*友元函数可以定义在类的内部*/} void X() { f(); } //错误:f还没有被声明 void g(); void h(); }; void X::g() { return f(); }//错误:f还没有被声明 void f() ; //声明那个定义在x中的函数 void x: :h() { return f(); }//正确:现在f的声明在作用域中了
这段代码的意义在于:友元声明在于影响访问权限,本身不是普通意义上的声明
5.类的作用域
5.1.1
类的外部,成员名字被隐藏,所以需要把类名写出
void window_mgr::clear (screenIndex i){ }
因为返回类型使用的名字在类的作用域之外,若返回类型是类类型,则需要指出它的类型
window_mgr ::screenIndex window_mgr ::addScreen (const screen &s)( }
5.1.2编译器处理完类中的全部声明后才会处理成员函数的定义。这样的结果就是成员函数能够使用类中所有的成员
5.1.3然而在类中,如果成员使用了外层作用域中的某个名字,而该名字代表一种类型,则类不能在之后重新定义该名字:
typedef double Money ;class Account { public: Money balance() { return bal;}// 使用外层作用域的 Money private: typedef double Money; //错误:不能重新定义Money Money bal; / / ... };
5.1.4成员与外层作用域的变量重名,要使用外层作用域的变量的方法
void screen :: dummy _fcn (pos height) { cursOr = width * ::height;//哪个height是那个全局的 }
6.再探构造函数
6.1构造函数与初始化
1、如果成员是 const、引用,或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值。
class ConstRef { public: ConstRef (int ii) ; private: int i; const int ci ; int &ri; }; //错误:ci和ri必须被初始化 ConstRef : : ConstRef (int ii) {//赋值: i = ii ; //正确 ci = ii; //错误:不能给const赋值 ri = i; //错误:ri没被初始化 } //正确:显式地初始化引用和const成员 ConstRef : : ConstRef(int ii): i(ii), ci(ii), ri(i) {}
2、最好令构造函数初始值的顺序与成员声明的顺序保持一致。而且如果可能的话,尽量避免使用某些成员初始化其他成员。
class x { int i; int j; public: //未定义的:i在j之前被初始化 x(int val): j(val), i(j){} }; //因为先声明了i,所以先执行i(j),就出错了
6.2委托构造函数(C++11新特性)
定义:它把它自己的一些(或者全部)职责委托给了其他构造函数。
在初始值列表使用了另外的构造函数
class sales_data { public: //非委托构造函数使用对应的实参初始化成员 Sales_data(std: :string s, unsigned cnt,double price): bookNo (s), units_sold(cnt) , revenue (cnt*price){ } //其余构造函数全都委托给另一个构造函数 Sales_data() : Sales_data("",0,o){} Sales_data (std::string s): Sales_data(s, 0,0){} Sales_data (std::istream &is) : Sales_data() { read (is,*this) ; } //其他成员与之前的版本一致 }; //受委托的函数执行完函数体才执行委托函数的函数体
6.3紧张构造函数定义的隐式转换(explicit)
关键字explicit 只对一个实参的构造函数有效。需要多个实参的构造函数不能用于执行隐式转换,所以无须将这些构造函数指定为explicit 的.只能在类内声明构造函数时使用explicit关键字,在类外部定义时不应重复:
sales_data item1 (null_book) ;//正确:直接初始化 //错误:不能将explicit构造函数用于拷贝形式的初始化过程 sales_data item2 = null_book;
为转换显式地使用构造函数:
//正确:实参是一个显式构造的Sales_data对象 item.combine (Sales_data (null_book)) ; //正确: static_cast可以使用explicit的构造函数 item.combine (static_cast<Sales_data>(cin));
6.4聚合类(使得用户可以直接访问其成员)
条件:1、所有成员都是public的 2、没有定义任何构造函数 3、没有类内初始值(参见2.6.1节,第64页) 4、没有基类,也没有virtual函数,关于这部分知识我们将在第15章详细介绍。
struct Data { int ival; string s; }; Data val1 = { o,"Anna" }; //如果初始值列表中的元素个数少于类的成员数量,则靠后的成员被值初始化------->数组也有这个功能 int str[10] = {1,2,3,4,5,6}//第六位往后全是0,输出s[9] = 0;
6.5字面值常量类
※6.6类的静态成员
6.6.1
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(); };
注意:1、每个Account对象将包含两个数据成员:owner和amount。只存在一个interestRate对象而且它被所有Account对象共享。
2、静态成员函数也不与任何对象绑定在一起,它们不包含this 指针。作为结果,静态成员函数不能声明成const的,而且我们也不能在static函数体内使用this指针。这一限制既适用于this的显式使用,也对调用非静态成员的隐式使用有效。
使用类的静态成员
//1、我们使用作用域运算符直接访问静态成员: double r; r = Account :: rate(); //2、虽然静态成员不属于类的某个对象,但是我们仍然可以使用类的对象、引用或者指针来访问静态成员: Account ac1; Account *ac2 = &ac1; //调用静态成员函数rate的等价形式 r = ac1.rate();//通过Account的对象或引用 r =ac2->rate();//通过指向Account对象的指针 //成员函数不用通过作用域运算符就能直接使用静态成员: class Account { public: void calculate() { amount += amount * interestRate; } private: static double interestRate;//其他成员与之前的版本一致 };
6.6.2定义静态成员
1、(函数)类内外都可以定义,类外定义不能再使用static,该关键字只出现在类内部的声明语句:
void Account :: rate (double newRate) interestRate = newRate; }
2、(成员变量)类内声明,类外定义,只能定义一次,其定义可以访问类内私有成员**
因为静态数据成员不属于类的任何一个对象,所以它们并不是在创建类的对象时被定义的。这意味着它们不是由类的构造函数初始化的。而且一般来说,不能在类内部初始化静态成员。相反的,必须在类的外部定义和初始化每个静态成员。和其他对象一样,一个静态数据成员只能定义一次。
//定义并初始化一个静态成员 double Account :: interestRate = initRate ( ) ; //从类名开始,这条定义语句的剩余部分就都位于类的作用域之内了
6.6.3类内初始化静态成员变量
条件:1、静态成员必须为字面值常量类型的constexpr 2、静态成员提供的初始值,必须为常量表达式
如果静态成员是整型或是枚举型const,则可以在类声明中初始化!!!
static const int vecSize = 20;//int类型可以用const初始化 //double类型的必须是constexprt constexprt static const double rate = 6.5;//或者static constexprt const double rate = 6.5
6.6.4静态成员特用场景
举个例子,静态数据成员可以是不完全类型(参见7.3.3节,第249页)。特别的,静态数据成员的类型可以就是它所属的类类型。而非静态数据成员则受到限制,只能声明成它所属类的指针或引用:
※※不完全类型:类在声明后定义前是不完全类似,就是不知道包含了哪些成员。(创建对象前类必须定义过,否则编译器不知道需要多少内存)所以一个类的成员不能是该类自己。一旦一个类的名字出现后,就是被声明过了,因此允许类包含指向自身类型的引用与指针。
class Bar { public: // ... private: static Bar meml;//正确:静态成员可以是不完全类型 Bar *mem2 ;//正确:指针成员可以是不完全类型 Bar mem3 ;//错误:数据成员必须是完全类型 };
非静态数据成员不能作为默认实参,因为它的值本身属于对象的一部分,这么做的结果是无法真正提供一个对象以便从中获取成员的值,最终将引发错误。
class screen { public: // bkground表示一个在类中稍后定义的静态成员 screen& clear(char = bkground); private: static const char bkground; };