啃书《C++ Primer Plus》 面向对象部分 构造函数基础及其使用 ——初始化列表 构造函数重载与调用 创建对象
发布日期:2021-06-23 17:48:05 浏览次数:3 分类:技术文章

本文共 4109 字,大约阅读时间需要 13 分钟。

啃书系列持续更新ing,关注博主一起xiao习鸭~

系列文章:


这一节进入面向对象内容的第二部分,来探讨学习一个非常重要且特殊的成员函数:构造函数。

如果您还不知道构造函数是什么,那么可以简单的理解为:构造函数是用来构造对象函数,用来初始化对象内部的数据,常在一个对象产生的时候被调用,用于“构造”这个对象。当然,它也不仅仅是在初始化的时候被调用,当对象需要被重新构造,如在赋值的时候,它也会被调用,这点在下面的文章中会有所体现。

本文会先介绍有关构造函数的创建相关问题。在创建构造函数后,再来介绍有关构造函数的一些使用。

下面是本节的思维导图。
在这里插入图片描述

here we go~


创建构造函数

学习使用构造函数的第一步,当然是创建构造函数。

构造函数的声明与定义

首先,构造函数的位置应该在其所属的类中,其声明与定义的格式如下:

声明(类内):	类名(参数列表);实现(类外):	类名::构造函数名(参数列表):初始化列表{};

可以看到,构造函数的声明与定义同一般的成员函数十分相近。在类内需要给出函数的形式,在类外实现时需要使用作用域解析符来表明从属关系。

通常,一个类的声明与定义我们分别写在头文件和对应的cpp文件中。现在,把它写成代码:

classA.hclass A{
public: A(int);private: int k; };
classA.cpp#include"classA.h"A::A(int k){
this->k = k;}

当然,上面的程序对于构造函数的实现方式不是固定的,您也可以选择使用 inline 修饰。或者干脆将它直接写在声明处(通常不建议这么做,声明和定义应该分别放置在头文件和.cpp文件以降低可能存在的编译依赖)。

从上面的程序可以看到构造函数的一些特点:

  • 构造函数没有返回类型,也不能返回任何值
  • 构造函数的函数名就是类名
  • 构造函数受访问限制
  • 构造函数具有初始化列表用以初始化成员
  • 构造函数带有this指针

对于前两条这里不过多解释,他们是构造函数的硬性规定。

第三点说到了构造函数的访问权限,在上面的例子中,访问权限是 public 的,这意味着可以在类外直接调用它。但是如果我们将访问权限改为 private 那么就只能在类内部使用它,在类外则不能使用这个构造函数。

下面我们就来分析一下第四、第五点带来的内容。

构造函数的初始化列表

构造函数的初始化列表的形式与位置如下:

初始化列表形式: 成员1(初始化值),成员2(初始化值)...初始化列表位置: 构造函数名称(参数列表):初始化列表{}

我们来举个粟子:

class B{
};class A{
public: A(int k = 0):k(10),y(k),b(),d(3.14159265){
}private: int k; int &y; double pi; B b;};

在这个粟子中,列举了构造函数初始化列表的使用。

需要重点注意的是:

  • 初始化列表开头需要写冒号!!!!
  • 初始化列表后需要有函数体!!!!

除了这两点注意以外,上面的例子还包含一些问题,一个是有关初始化顺序的,还有就是有关对象成员的初始化问题。

下面我们先来介绍下成员初始化顺序的规则。有关初始化更进一步的内容,会在下文谈论对象创建时说到。

成员初始化顺序

成员的初始化顺序这一点很重要 (这还是面试的经典问题嘞)

首先需要说明一点的是,成员的初始化顺序与初始化列表的顺序无关,而是由成员在类中的声明顺序决定的。(初始化列表:这事雨我无瓜)

实际上,程序在一开始会获取一个类的信息,其中的成员是顺次存放的。而在创建对象时,所有的成员都需要初始化 (这好像是句废话) ,在执行构造函数的函数体前,程序会依次拿到成员并参照初始化列表对其进行初始化。对于一个成员,如果在初始化列表中存在初始化则按照初始化列表,如果没有,则按照声明时写入的的初始化方式,否则使用默认的初始化方式。

没理清楚?不要慌,看个粟子,你就明白了:

#include
using namespace std;class A{
public: A(int k = 99):b(k),a(b){
} int a; int b;};int main(){
A a; cout << "a is " << a.a << endl; cout << "b is " << a.b << endl;}

对于以上例子,我们使用成员b的值来初始化a,使用传入参数k的值初始化b。对于结果

  • 如果是按照初始化列表的顺序,那么结果应该是 a=99,b=99
  • 如果是按照声明的顺序,那么结果应该是 a=默认初始化值(一个随机出现的数字),b=99

我们运行程序,便见分晓:

在这里插入图片描述
你品,你细品~

this指针

接着说说this指针在构造函数中的使用。this指针是每个成员函数都具有的隐藏参数,指向对象自身。

在构造函数中常使用this指针可以解决参数与成员重名的问题:

class A{
public: A(int k = 0){
this->k = k;} int k;};

构造函数的重载及调用

利用著名的三段论:

  • 函数可以重载
  • 构造函数是函数
  • 构造函数可以重载

同一般函数相似的是,当参数列表不同构造函数同时出现时,就会产生重载,看个粟子

class A{
public: A(int); A();private: A(int,double); int k; double d;};

重载构造函数,可以为类实例对象创造多种方式,在实例化对象时可以选择传入不同的数据,调用不同的构造函数进行构造。

这里最特殊的,还是默认的无参构造函数。

默认无参构造函数

首先需要明确一点: 当类中没有显式的声明构造函数时,编译器会提供一个默认的无参构造函数,否则编译器不会提供。 这句话告诉我们一个常见的错误,就是忘记无参构造函数的声明。

例如:

class A{
public: A(int k){
this->k = k;} int k;};int main(){
A a;}

这段程序将会报错,因为程序找不到无参构造函数。

正确的操作应该是添加一个无参构造函数,或者利用参数缺省值满足对无参构造函数调用的兼容:

class A{
public: A(int k = 0){
this->k = k;} int k;};
构造函数中调用另一个构造函数

重载函数,避免不了要在函数中调用同名的函数进行处理。

在一般的函数中,调用同名重载函数只需要按照正常的语法进行调用:

int f(int a){
return a * a;}int f(){
return f(10);}

但是这样的调用在构造函数中却是不允许的(至少C++是这样)。 也就是说,下面的代码非法:

class A{
public: A(){
A(10);} A(int k){
this->k = k;} int k;};

使用重载构造方法的正确的打开方式,应该是在初始化列表中调用:

class A{
public: A():A(10){
} //在初始化列表中调用另一个构造函数 A(int k){
this->k = k;} int k;};

最后,需要注意的是,若在初始化列表中调用了另一个构造函数,那么这个初始化列表将不能再有任何对象的初始化。否则编译器会报错!!!


使用构造函数

介绍过构造函数的基础,下面就可以考虑它的使用了。让我们从使用构造函数创建一个对象开始!

创建对象

区别于内置类型的创建,每个对象在创建的时候,都会使用构造函数。基本写法如下:

类名 对象名 = 类名(参数列表);类名 对象名(参数列表);
创建形式

上面的写法分为两种,其中括号的部分相当于对构造函数的一种调用。

前者是对构造函数的显式调用,后者是对构造函数的隐式调用

特殊的,如果调用的是无参构造函数,则隐式调用可以省略括号

类名 对象名;

但是显式调用仍需要带上空括号

类名 对象名 = 类名();

粟子:

class A{
public: A(){
this->k = 10;} A(int k){
this->k = k;} int k;};int main(){
A c(20); //隐式调用 A d = A(20); //显示调用 A a; //隐式调用无参构造函数 A b = A(); //显式调用无参构造函数}
对象成员的初始化

说到对象创建,最好来谈论下有关对象成员初始化的问题。即对于不同类别的成员,可以以何种方式进行初始化。

常见的对象有这么几种:1. 内置类型2. 类对象以及他们的:1. 指针2. 引用3. 常量

按照博主的经验:

  • 内置类型,可以选择在初始化列表中进行初始化,也可以先使其进行默认初始化再在构造方法中进行赋值。
  • 类对象,通常选择在初始化类表中调用其某个构造函数进行初始化,再在构造函数体中进行剩下的修改。
  • 引用和常量,这两种量必须进行初始化,所以必须在初始化列表中对其进行初始化,否则会报错。
  • 指针,如果是被 const 修饰指针本身(指针类型 const),那么它也必须在初始化列表中进行初始化。否则可按照内置类型方式处理。

总之就是:

必须进行初始化的成员需要在初始化列表中进行初始化
对象成员最好在初始化列表中初始化(否则调用其无参构造函数)
内置类型以及不被限定访问的指针可以不必在初始化列表中体现


类型转换

有关类型转换的内容,由于仅使用构造函数不能完全说明类型转换的内容,因此博主打算与转换函数放在一起进行介绍,本篇内容就先到这里。

非常感谢您的观看,如有错误,欢迎指出哦。


博文看完了,确定不点个收藏或者关注再走嘛~~~点个收藏关注嘛~~~点一下就好~~~求你了QAQ

转载地址:https://wayne-lee-jlu.blog.csdn.net/article/details/105339552 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:企业中台建设中Maven Pom治理最佳实践
下一篇:Spring Cloud Alibaba 版本对照详情

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2024年05月03日 20时00分50秒