啃书《C++ Primer Plus》 面向对象部分 构造函数基础及其使用 ——初始化列表 构造函数重载与调用 创建对象

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

系列文章:
啃书《C++ Primer Plus》之 C++ 函数指针
啃书《C++ Primer Plus》之 C++ 名称空间1
啃书《C++ Primer Plus》之 C++ 名称空间2
啃书《C++ Primer Plus》之 C++ 引用
啃书《C++ Primer Plus》之 const修饰符修饰 类对象 指针 变量 函数 引用
啃书《C++ Primer Plus》之 枚举 内容大全
啃书《C++ Primer Plus》之 内存模型(上) 变量的存储持续性 作用域 连接性


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

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

本文会先介绍有关构造函数的创建相关问题。在创建构造函数后,再来介绍有关构造函数的一些使用。
下面是本节的思维导图。
在这里插入图片描述

here we go~


创建构造函数

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

构造函数的声明与定义

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

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

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

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

classA.h
class A
{
publicA(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<iostream>
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

猜你喜欢

转载自blog.csdn.net/wayne_lee_lwc/article/details/105339552