C++Primer 学习(类 一)类的基础_内置的赋值运算符把它的左侧运算对象当成左值返回

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

//下面的代码是非法的:因为我们不能显式地定义自己的this指针
//谨记此处的this是一个指向常量的指针,因为isbn是一个常量成员
std: :string Sales_data::isbn (const Sales_data *const this)
{
return this->isbn;
}


因为this是指向常量的指针,所以常量成员函数不能改变调用它的对象的内容。在上例中,isbn可以读取调用它的对象的数据成员,但是不能写入新值。**注意:常量对象,以及常量对象的引用或指针都只能调用常量成员函数。**


**1.3 类作用域和成员函数**


类本身就是一个作用域。类的成员函数的定义嵌套在类的作用域之内,因此, isbn中用到的名字bookNo其实就是定义在Sales data内的数据成员。值得注意的是,即使bookNo定义在isbn之后,isbn也还是能够使用bookNo。


\*\*因为:编译器分两步处理类:首先编译成员的声明,然后才轮到成员函数体。\*\*因此,成员函数体可以随意使用类中的其他成员而无须在意这些成员出现的次序。


**1.4 定义一个返回this对象的函数**


函数combine的设计初衷类似于复合赋值运算符+=,调用该函数的对象代表的是赋值运算符左侧的运算对象,右侧运算对象则通过显式的实参被传入函数:



Sales_data& Sales_data::combine (const Sales_data &rhs)
{
units_sold += rhs.units_sold; //把rhs的成员加到this对象的成员上
revenue += rhs. revenue;
return *this;//返回调用该函数的对象
}

total. combine (trans);//更新变量total当前的值


total的地址被绑定到隐式的this参数上,,而rhs绑定到了trans上。因此,当combine执行下面的语句时:



units_sold += rhs.units_sold; //把rhs的成员添加到this对象的成员中


效果等同于求total.units sold和trans.unit sold的和,然后把结果保存到total.units sold中。


\*\*该函数一个值得关注的部分是它的返回类型和返回语句。\*\*一般来说,当我们定义的函数类似于某个内置运算符时,应该令该函数的行为尽量模仿这个运算符。内置的赋值运算符把它的左侧运算对象当成左值返回,因此为了与它保持一致,**combine函数必须返回引用类型**。因为此时的左侧运算对象是一个Sales data的对象,所以返回类型应该是Sales\_data&


如前所述,我们无须使用隐式的this指针访问函数调用者的某个具体成员,而是需要把调用函数的对象当成一个整体来访问:



return *this;//返回调用该函数的对象


其中,return语句解引用this指针以获得执行该函数的对象,也就是说:上面的这个调用返回total的引用。


**2. 定义类相关的非成员函数**


类的作者常常需要定义一些辅助函数,比如add、read和print等。尽管这些函数定义的操作从概念上来说属于类的接口的组成部分,但它们实际上并不属于类本身。我们定义非成员函数的方式与定义其他函数一样,通常把函数的声明和定义分离开来。如果函数在概念上属于类但是不定义在类中,则它一般应与类声明(而非定义)在同一个头文件内。在这种方式下,用户使用接口的任何部分都只需要引入一个文件。


**一般来说,如果非成员函数是类接口的组成部分,则这些函数的声明应该与类在同一个头文件内。**


**3. 构造函数**


每个类都分别定义了它的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数(constructor)。构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。


**构造函数的名字和类名相同**。**和其他函数不一样的是,构造函数没有返回类型;除此之外类似于其他的函数,构造函数也有一个(可能为空的)参数列表和一个(可能为空的)函数体。**


类可以包含多个构造函数,和其他重载函数差不多,不同的构造函数之间必须在参数数量或参数类型上有所区别。不同于其他成员函数,**构造函数不能被声明成const的**。当我们创建类的一个const对象时,直到构造函数完成初始化过程,对象才能真正取得其“常量”属性。因此,构造函数在const对象的构造过程中可以向其写值。


**3.1 合成的默认构造函数**


但是之前的Sales data类并没有定义任何构造函数,可是之前使用了Sales data对象的程序仍然可以正确地编译和运行。比如定义了两个对象:



Sales_data total;//保存当前求和结果的变量
Sales_data trans;//保存下一条交易数据的变量


那么 total和trans是如何初始化的呢?我们没有为这些对象提供初始值,因此我们知道它们执行了默认初始化。类通过一个特殊的构造函数来控制默认初始化过程,这个函数叫做默认构造函数(default constructor)。默认构造函数无须任何实参。


**默认构造函数在很多方面都有其特殊性。**其中之一是,如果我们的类没有显式地定义构造函数,那么编译器就会为我们隐式地定义一个默认构造函数。编译器创建的构造函数又被称为**合成的默认构造函数( synthesized defaultconstructor)**。


对于大多数类来说,这个合成的默认构造函数将**按照如下规则初始化类的数据成员**:如果存在类内的初始值用它来初始化成员,否则,默认初始化该成员。


我们来为Scale\_data定义构造函数:



struct Sales_data
{
//新增的构造函数
Sales_data () = default;
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) {}
Sales data (std: :istream &);
//之前已有的其他成员
std: :string isbn () const{return bookNo;}
Sales_data& combine (const Sales_data&) ;
double avg_price () const;
std: :string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};


**3.2 =default 的含义**


下面这种写法:



Sales_data () = default;


首先请明确一点:因为该构造函数不接受任何实参,所以它是一个默认构造函数。我们定义这个构造函数的目的:仅仅是因为我们既需要其他形式的构造函数,也需要默认的构造函数。我们希望这个函数的作用完全等同于之前使用的合成默认构造函数。


在C1+11新标准中,如果我们需要默认的行为,那么**可以通过在参数列表后面写上=default来要求编译器生成构造函数。**


其中,= default既可以和声明一起出现在类的内部,也可以作为定义出现在类的外部。和其他函数一样,如果= default在类的内部,则默认构造函数是内联的;如果它在类的外部,则该成员默认情况下不是内联的。


**注意:**


上面的默认构造函数之所以对Sales data有效,是因为我们为内置类型的数据成员提供了初始值。如果你的编译器不支持类内初始值,那么你的默认构造函数就应该使用构造函数初始值列表来初始化类的每个成员。


**3.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) {}


我们把新出现的部分称为构造函数初始值列表(constructor initialize list),它负责为新创建的对象的一个或几个数据成员赋初值。


units sold和revenue则没有显式地初始化。当某个效据成员被构造函数初始值列表忽略时,它将以与合成默认构造函数相同的方式隐式初始化。


在此例中,这样的成员使用类内初始值初始化,因此只接受一个string参数的构造函数等价于:



//与上面定义的那个构造函数效果相同
Sales_data (const std: :string &s):bookNo (s), units_sold (0), revenue (0){}


通常情况下,构造函数使用类内初始值不失为一种好的选择,,因为只要这样的初始值存在我们就能确保为成员赋予了一个正确的值。


不过,如果你的编译器不支持类内初始值,则所有构造函数都应该显式地初始化每个内置类型的成员。


**注意:**


**构造函数不应该轻易覆盖掉类内的初始值,除非新赋的值与原值不同。如果你不能使用类内初始值,则所有构造函数都应该显式地初始化每个内置类型的成员。**


**4. 拷贝、赋值和析构**


除了定义类的对象如何初始化之外,类还需要控制拷贝、赋值和销毁对象时发生的行为。


对象在几种情况下会被拷贝,如我们初始化变量以及以值的方式传递或返回一个对象等。


![img](https://img-blog.csdnimg.cn/img_convert/bc3d0678ad9f0dd637c107079e39b0ba.png)
![img](https://img-blog.csdnimg.cn/img_convert/55e66a132658f8f8f6da1193cf3529ad.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

1715735309739)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值