C++primer类的基础精讲

2022-11-13 11:11:04 基础 primer

定义抽象数据类型

初探this和

struct Sales_data
{
    string isbn(){return bookNo;}
    Sales_data & combine(const Sales_data&);
    double avg_price() const;
    string bookNo;
    unsigned units_sold=0;
    double revenue=0;
};
Sales_data total;

引入this

对于isbn成员函数的调用: total.isbn();

当我们调用成员函数时,实则上是在替某个对象调用它。在上面的调用中,当isbn返回bookNo时,实际上隐式地返回total.bookNo.

成员函数通过一个名为this的额外隐式参数来访问调用它的那个对象。当我们调用一个成员函数时,用请求该函数的对象地址初始化this。例如,如果调用total.isbn(),编译器负责把total的地址传递给isbn的隐式形参this,可以等价认为编译器将该调用重写成Sales_data::isbn(&total),调用Sales_data时的isbn成员时传入了total的地址。

在成员函数内部,我们可以直接使用调用该函数的对象的成员,而无须通过成员访问运算符来做到这一点,因为this所指的正是这个对象。任何对类成员的直接访问都被看做this的隐式调用,例如,isbn在使用bookNo时,隐式地使用this指向的成员,就如同this->bookNo一样。

构造函数

定义:类通过一个或几个特殊的成员函数来控制其对象的初始化,这些函数叫做构造函数。

无论何时,只要类的对象被创建,就会执行构造函数。

构造函数的名字和类名一样,构造函数没有返回类型,一个类可以拥有多个构造函数,但每个构造函数之间必须在参数数量或参数类型上存在不同。且构造函数不能被声明成const。

当一个类没有定义任何构造函数时,编译器会给类自动添加一个默认构造函数,该构造函数无须任何实参对对象进行初始化。

对前面的Sales_data类进行编写构造函数

struct Sales_data
{
    Sales_data()=default;
    Sales_data(const string &s):bookNo(s)()
    Sales_data(const string &s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}
    Sales_data(istream &)
    string isbn() const{return bookNo;}
    Sales_data &combine(const Sales_data&);
    double avg_price() const;
    string bookNo;
    unsigned units_sold=0;
    double revenue=0.0;
}

(1)=default的含义

如果需要默认构造函数起作用,那么可以在参数列表后面写上=default来要求编译器生成默认构造函数。

(2)构造函数初始值列表

Sales_data(const string &s):bookNo(s)()
Sales_data(const string &s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}

上面出现了两个新的构造函数的写法,该部分称为构造函数初始值列表。

负责为新创建的对象的一个或几个数据成员赋初值。构造函数初始值是成员名字的一个列表,每个名字后面紧跟括号括起来的成员初始值。

当某个数据成员被构造函数初始值列表忽略时,他将以合成默认构造函数相同的方式隐式初始化。所以,第一个构造函数等价于:

Sales_data(const string &s):bookNo(s),units_sold(0),revenue(0)();

访问控制和封装

访问控制符public和private

定义在public说明符之后的成员在整个程序可被访问。

定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问。

Sales_data类的新形式

class Sales_data
{
public:
    Sales_data()=default;
    Sales_data(const string &s):bookNo(s)()
    Sales_data(const string &s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}
    Sales_data(istream &)
    string isbn() const{return bookNo;}
    Sales_data &combine(const Sales_data&);
private:
    double avg_price() const;
    string bookNo;
    unsigned units_sold=0;
    double revenue=0.0;
}

友元

类允许其他类或者函数访问他的非公有成员,方法是令其他类或者函数成为他的友元。如果一个类想把一个函数作为他的友元,只需要增加一个friend关键字开始的函数声明语句即可

class Sales_data
{
    friend Sales_data add(const Sales_data &,const Sales_data&);
    friend istream &read(istream&,Sales_data&);
    friend ostream &print(ostream&,const Sales_data&)
public:
    Sales_data()=default;
    Sales_data(const string &s):bookNo(s)()
    Sales_data(const string &s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}
    Sales_data(istream &)
    string isbn() const{return bookNo;}
    Sales_data &combine(const Sales_data&);
private:
    double avg_price() const;
    string bookNo;
    unsigned units_sold=0;
    double revenue=0.0;
}

友元的声明只能出现在类定义的内部。友元不是类的成员,也不受他所在区域访问控制级别的约束。

类的其他特性

可变数据成员

在一个const成员函数中,若希望修改类的某个数据成员,可以通过在变量的声明中加入mutable关键字实现

class screen{
public:
        void some_menmber() const;
private:
        mutable size_t access_ctr
};
void screen::some_member() const
{
    ++access_ctr
}

返回*this的成员函数

inline Screen &Screen::set(char c)
{
    contents[cursor]=c;
    return *this;
}
inline Screen &Screen::set(pos r,pos col,char ch)
{
    contents[r*width+col]=ch;
    return *this;
}
inline Screen &Screen::move(pos r,pos c)
{
    pos row=r*width;
    cursor=row+c;
    return *this;
}

move和set一样,返回的值是对对象的引用。

myScreen.move(4,0).set('#');

等同于

myScreen.move(4.0);

myScreen.set('#');

假如我们定义的返回类型不是引用,则move的返回值将是*this的副本,因此调用set只能改变临时副本,不能改变myScreen的值

友元类

例如,window_mgr类的某些成员需要访问screen类的内部数据,例如window_mgr的clear函数将一个指定的screen类的内容设置为空白。

class Screen{
//window_mgr的成员可以访问Screen类的私有部分
friend class Window_mgr
//Screen类剩余部分
}
class Window_mgr{
public:
    using ScreenIndex=std::vector<Screen>::size_type
    void clear(ScreenIndex);
private:
    std::vector<Screen> screens{Screen(24,80,' ')}
};
void Window_mgr::clear(ScreenIndex)
{
    Screen &s=screen[i];
    s.contens=string(s.height*swidth,' ')
}

构造函数再探

构造函数初始值列表

(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),ri(i){};

如果成员是const,引用,或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值。

(2)成员初始化顺序

成员初始化的顺序与他们在类定义中的出现顺序一致。一般来说初始化的顺序没什么特别要求,不过如果一个成员是用另一个成员来初始化的,那么着两个成员的初始化顺序就关键了。

例如

class X
{
    int i;
    int j;
public:
    X(int val):j(val),i(j){}
};

而编译器实际上是先初始化i,在初始化j,而初始化i的时候发现j没有值,所以上述构造函数会发生错误。

默认构造函数的作用

当对象被默认初始化或值初始化时自动执行默认构造函数。

默认初始化在以下情况下发生:

(1)当我们在块作用域内不适用任何初始值定义一个非静态变量或数组时。

(2)当一个类本身含有类类型的成员且使用合成的默认构造函数时

(3)当类类型的成员没有在构造函数初始值列表中显示地初始化时

值初始化在以下情况发生:

(1)在数组初始化的过程中如果我们提供的初始值数量少于数组的大小时

(2)当我们不适用初始值定义一个局部静态变量

使用默认构造函数

下面的obj的声明可以正常通过

Sales_data obj();//正确,定义了一个函数而非对象
if(obj.isbn()==primer_5th_ed.isbn())//错误:obj是一个函数

但当我们试图使用obj时,编译器将报错,提示我们不能使对函数使用成员访问运算符。因为obj的实际含义是一个不接受任何参数的函数并且其返回值是Sales_data类型的对象。

如果想定义一个使用默认构造函数进行初始化的对象,正确方法是去掉对象名之后的空括号对。

Sales_data obj;

聚合类

满足一下条件的类是聚合类:

(1)所有成员都是public的

(2)没有定义任何构造函数

(3)没有类内初始值

(4)没有基类,也没有虚函数

例:

struct Data
{
    int val;
    string s;
};

类的静态成员

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和amout。只存在一个interestRate对象而且他被所有account对象共享。

静态成员函数也不与任何对象绑定在一个,他们不包含this指针。作为结果,静态成员函数不能声明成const的,而且我们也不能在static函数体内使用this指针。

到此这篇关于c++ primer类的基础精讲的文章就介绍到这了,更多相关C++类内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

相关文章