目录
? 在?C++基础(3)——类与对象-CSDN博客中首次引入了构造函数的概念,下面给出一个构造函数:
(注:笔者在下面的代码中将构造函数放到一个命名空间中,是因为此构造函数与笔者代码中的文件有了冲突,如果读者需要根据复制下方代码,将命名空间删除即可。)
namespace violent
{
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
}
? ? ? ?构造函数的主要作用是用于初始化对象,即给对象中的成员变量一个初始值。但是上述行为并不能称为对象的初始化,而是给对象中的成员变量赋初值。对于成员变量的初始化,需要借助初始化列表来完成:
? ? ? ? 以冒号开始,逗号用于分隔每一个成员变量,每一个成员变量后面根一个括号,括号里面是初值或者表达式,例如:
namespace violent
{
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
}
? ? ? ?为了后续表述,此处,将在构造函数函数体内部进行的操作称为赋初值,将初始化列表部分的操作称之为初始化。
? ? ? 不难发现,对于_,_,_这种内置类型,赋初值或者初始化都可以达成创建构造函数的目的。但是,对于以下三种类型的变量,则必须使用初始化列表进行初始化,而不能赋初值。这三种类型的变量分别是:成员变量、引用成员变量、自定义类型变量(且没有默认构造函数)例如,当在函数体内部来对上述类型赋值想要达到初始化的目的时,即:
?
namespace violent
{
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{
_i = 1;
j = 1;
}
private:
int _year;
int _month;
int _day;
int& _i;
const int j;
};
}
运行代码,此时编译器显示:
? ? ?代码运行显示,这两个类型的变量必须初始化,对于引用,在前面的文章C++基础——对于C语言缺点的补充(2)-CSDN博客?提到了引用的几个特点,其中有一条为:引用必须初始化,存在野指针,但是并不存在野引用。而对于类型的变量,由于其在后续不能更改的特性,因此创建时必须进行初始化,而在文章开头提到,在构造函数的函数体内部进行的操作并不是真正意义上的初始化,而是赋值,因此,对于上述类型,必须在初始化列表中完成初始化,即:
?
namespace violent
{
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
, _i(month)
, j(year)
{
}
private:
int _year;
int _month;
int _day;
int& _i;
const int j;
};
}
? ? ? ? ?对于初始化列表初始化以及构造函数函数体内赋值,这两种方法可以混合使用,例如:
namespace violent
{
class Date
{
public:
Date(int year, int month, int day)
: _i(month)
, j(year)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
int& _i;
const int j;
};
}
? ? ? ?对于上述代码,_、_、_虽然没有在初始化列表中显性定义,但是这些变量同样也会被自动定义,对于内置类型,会给予随机值,而对于自定义类型,则会去调用这个自定义类型的默认构造函数
(注:默认构造函数一般认为有三种:全缺省、不用传递参数、编译器自动生成)
? ? ? ? 为了方便演示不在初始化列表中显示定义自定义类型的效果,额外创建另一个自定义类型,即:
class A
{
public:
A(int a = 0)
:_a(a)
{}
private:
int _a;
};
? ? ? ? ? 并对命名空间中的类做下面的修改:
namespace violent
{
class Date
{
public:
Date(int year, int month, int day)
: _i(month)
, j(year)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
int& _i;
const int j;
A _a;
};
}
? ? ? ? 通过监视窗口监视类中的成员变量,当代码进入构造函数时,即:
? ? ? ?各个变量的值如下:
? ? ? ?不难看到,此时没有在初始化列表中显示定义的内置类型,被编译器自动给予了随机值。
使用?让代码继续调试,当调试到下方图片展示的行数时:
? ? ? ?各个变量的值如下:
? ? ? 继续向下调试,此时代码不是直接向下继续运行,而是转去运营类中的默认构造函数,即:
? ? ? ?当运行完类后,会去自动回到类继续运行,即:
? ? ? ?此时各个变量的值为:
? ? ? ?通过此处可以看到,虽然初始化列表中并没有显示定义自定义类型_的初始化,但是此时的自定义类型_被初始化为0?,证明了对于自定义类型,如果不再初始化列表中显示定义其初始化,则编译器会去自动调用其默认构造函数。
? ? ? 上面的情况中,自定义类型中含有其自己的默认构造函数,当自定义类型中没有默认构造时,即,将类中的默认构造函数由全缺省改为需要传递参数,即:
?
class A
{
public:
A(int a)
:_a(a)
{
cout << "class A" << endl;
}
private:
int _a;
};
此时如果再运行代码;编译器会显示报错,即:
? ? ? ?在C++基础(3)——类与对象-CSDN博客中介绍构造函数时提到,对于内置类型,构造函数不作用,对于自定义类型会生成或者调用其自己的构造函数,而为了防止内置类型不能初始化,于是引入了缺省值来解决这个问题。缺省值,与初始化列表由一定的联系,为了解释二者之前的联系,文章首先给出没有缺省值时,构造函数的运行顺序:
1.
2.
3.
4.?
5.
(注:解释此部分与自定义类型的初始化无关,故直接跳过自定义类型的初始化)
6.?
随后代码继续向下运行,给三个成员变量赋初值。
当给成员变量_缺省值后,即:
int _year = 10;
此时再调试代码,顺序如下:
1.
2.?
? ? ? ?此时,代码运行的顺序发生了改变,直接去运行给予缺省值的代码,?并且,此时_不再是随机值,而是缺省值,而没有给缺省值的两个成员变量依旧是随机值
此时继续向下运行代码:
代码重新回到没有缺省值运行时的第二步
以上针对一个成员变量给予了缺省值,假如,这个有缺省值的成员变量再初始化列表中显示定义,即:
namespace violent
{
class Date
{
public:
Date(int year, int month, int day)
:_year(2)
, _i(month)
, j(year)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year = 10;
int _month;
int _day;
int& _i;
const int j;
A _a;
};
}
?代码的运行顺序如下:
1.
2.?
? ? ? ?此时可以观察到,代码的运行顺序又发生了改变,不运行带有缺省值的代码,而是直接运行初始化列表中显示定义的代码,且本行代码执行完毕后,成员变量_?的值为:
通过上述例子,可以总结初始化列表与缺省值的关系:
? ? ? 缺省值主要是针对于初始化列表,若不在初始化列表中对内置类型显示定义,则有缺省值的成员变量优先使用缺省值,没有缺省值的变量会被编译器赋随机值。在初始化列表对内置类型显示定义的情况下,则不使用缺省值。
对于下面给出的代码:
namespace violent2
{
class A
{
public:
A(int a)
:_a1(a)
, _a2(_a1)
{}
void Print() {
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2;
int _a1;
};
int main() {
A aa(1);
aa.Print();
}
}
? ? 需要注意,在初始化列表中,虽然_在前,但是初始化列表初始化的顺序是由成员变量的声明的顺序决定的,与初始化列表无关,由此可见,初始化列表初始化的顺序为
, _a2(_a1)
:_a1(a)
对于下面给出的代码,可以利用全局变量来知道代码在运行过程创建了多少对象:
#include<iostream>
using namespace std;
namespace violent
{
int count = 0;
}
class A
{
public:
A() { ++violent::count; }
A(const A& dd) { ++violent::count; }
~A(){};
private:
};
A fun()
{
A aa;
return aa;
}
int main()
{
A a;
fun();
cout << violent::count << endl;
return 0;
}
运行结果如下:
对于上述情况中,全局变量可以达成统计创建对象次数的目的。但是,使用全局变量这种方式仍然存在一定的短板,例如,当代码中不知存在一个类时,例如:
?
#include<iostream>
using namespace std;
namespace violent
{
int count = 0;
}
class A
{
public:
A() { ++violent::count; }
A(const A& dd) { ++violent::count; }
~A(){};
private:
};
class B
{
public:
B() { ++violent::count; }
B(const B& d1) { ++violent::count; }
~B() { ++violent::count; }
private:
};
A fun()
{
A aa;
return aa;
}
int main()
{
A a;
fun();
B b;
cout << violent::count << endl;
return 0;
}
此时运行结果为:
? ? ? ? 由于是全局变量,因此,结果是两个类创建的对象的数量的总和,并不能统计各个类创建的对象数目。对于此问题的解决办法,就是将用于统计创建对象数目的变量作为一个类的成员变量,例如:
class A
{
public:
A() { ++count; }
A(const A& dd) { ++count; }
~A(){};
private:
int count;
};
?但是,对于此办法仍然存在问题,因为此时的成员变量的统计结果是相互独立的,即:
A fun()
{
A aa;
return aa;
}
int main()
{
A a;
fun();
;
return 0;
}
对于创建对象和创建对象时,二者的结果是相互独立的。而想要统计的是整个类在创建对象时的值,此时,如果对代码做出下列更改:
int main()
{
A a;
fun();
;
cout << A::count << endl;
return 0;
}
编译器会报错,并显示下列内容:
为了解决上述问题,需要引入成员
具体使用方法如下:
#include<iostream>
using namespace std;
class A
{
public:
A() { ++count; }
A(const A& dd) { ++count; }
~A(){};
public:
static int count;
};
int A::count = 0;
A fun()
{
A aa;
return aa;
}
int main()
{
A a;
fun();
;
cout << A::count << endl;
return 0;
}
在使用时,需要注意以下几个点:
? ? ? ?1.?成员需要现在类中进行声明,并且在声明的时候要加上关键字,即:
static int count;
? ? ? ?在类中声明后,需要在类外进行定义,即:
int A::count = 0;
? ? ? ?2. 对于一个类中的成员而言,它并不属于一个特定的对象,而是为类的所有对象共享,因此,在对成员的声明时,不能像声明常规的成员变量一样给缺省值。因此缺省值针对的是构造函数中的初始化列表,而初始化列表是针对对某个对象中的成员。不适用于成员这种为类所有对象共享的成员。
? ? ? ?对于一个成员的访问,在类的访问限定符的类型为的情况下,有两种访问方式,一种针对于类,另一种针对于某个对象,例如:
int main()
{
A a;
fun();
cout << A::count << endl;
cout << a.count << endl;
return 0;
}
? ? ? ? ?运行结果为:
? ? ? ?通过打印结果可以看到,不管通过何种方式对成员进行访问,其打印结果都是一样的。
在类访问限定符为的情况下,即私有的情况下,访问成员变量的方法有如下几个:?
? ? ? ? 1. 提供一个供类外访问成员变量的函数,为了方便表述,此处将这个函数命名为:
using namespace std;
class A
{
public:
A() { ++count; }
A(const A& dd) { ++count; }
~A(){};
int Get()
{
return count;
}
private:
static int count;
};
int A::count = 0;
A fun()
{
A aa;
return aa;
}
int main()
{
A a;
fun();
cout << a.Get() << endl;
return 0;
}
运行结果如下:
? ? ? ?不过,对于此种的访问方法,需要额外创建一个对象。如果,想要只通过类而不通过具体的对象来访问这个成员变量,则可以使用成员函数来完成,具体操作方法,只需要在类中已有函数的的类型前面加上关键字即可:
class A
{
public:
A() { ++count; }
A(const A& dd) { ++count; }
~A(){};
static int Get()
{
return count;
}
private:
static int count;
};
int A::count = 0;
A fun()
{
A aa;
return aa;
}
int main()
{
fun();
cout << A::Get() << endl;
return 0;
}
? ? ? 对于成员函数,其最大的特点就是没有隐藏的指针,而在类中,任何成员变量或者成员函数调用以及访问都是通过指针达成的,因此,在成员 函数中,不能访问非类型的成员变量以及非类型的成员函数
? ? ? ?由于个人能力有限,书中难免出现汉字拼写错误、代码意义解释错误、内容逻辑以及理解错误等不同类型的错误。首先感谢各位大佬能花掉自己宝贵的时间阅读此文章,愿大佬们斧正,发现错误可以通过私信联系,本人不胜感激。