C++primer 第七章一些笔记

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;
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值