概述
在C++中,类与C语言中的结构体类似,类与结构体的不同之处便是在其内部多了几个成员函数还有几个访问限定符,访问限定符有public(公共)、protected(保护)、private(私有),而成员函数总的来说共包括六大类,他们便是类与结构体的不同之处,六大默认成员函数分别是构造函数,拷贝构造函数,析构函数,赋值操作符重载,取地址操作符重载和const修饰的取地址操作符重载。
刨根问底之三大访问限定符
每个限定符在类体中可以使用多次,它的作用域是从该限定符出现开始到下一个限定符之前或类体结束前
;如果一个类中对变量无显式限定符,那么编译器将默认其private类型的.
public
顾名思义,public公共的就是任何人都可以用的,不管是类中的使用或是类外的使用都是被允许的.
protected与private
protected与private体现了面向对象中的封装性
,被protected修饰或private修饰的变量,而
protected和private的差别在于private在子继承时也不可访问
,不存在继承性。
在这里也顺便提下作用域:作用域有局部域,类域,全局域和名字空间域;在类体外定义成员,需要使用“::”作用域解释符指明成员属于哪个类域.
刨根问底之默认成员函数
借鉴
构造函数与析构函数的起源
作为比C更先进的语言,C++提供了更好的机制来增强程序的安全性。C++编译器具有严格的类型安全检查功能,但程序通过了编译检查并不表示已经没有了错误,在错误的大家庭里,“语法错误”的地位只能算是小弟弟,而“有水平”的错误通常隐藏的很深。不少难以察觉的程序错误是由于变量没有被正确初始化或清除造成的,而初始化和清除工作很容易被人遗忘。Bjarne Strostrup 在设计C++语言时考虑到了这个问题并很好地加以解决.
把对象的初始化工作放在构造函数中,把清除工作放在析构函数中。当创建对象时,构造函数被自动执行;当对象消亡时,析构函数被自动执行。这样就不用担心忘记对象的初始化和清除工作了。
构造函数与析构函数的名字必须让编译器认得出才可以被自动执行,所以Bjarne Strostrup的这样命名:
让构造函数、析构函数与类同名,但析构函数与构造函数目的相反,所以在析构函数前加上前缀“~”(取“求反”之意)
(取自《高质量C/C++》)
构造函数
定义
(1)函数名与类名相同;
(2)无返回值;
(3)对象构造(对象实例化)时系统自动调用对应的构造函数;
(4)构造函数可以重载(所以在一个类体内,不一定只有一个构造函数);
(5)构造函数可以在类中定义,也可以在类外定义;
(6)如果没有给出构造函数,则C++编译系统会自动生成缺省构造函数;
(7) 无参与全缺省值构造函数都是构造函数,但他们在类体内不能同时出现.
成员的初始化列表
一般我们认为在构造函数体内来初始化数据成员,然而这不是真正意义上的初始化,而是赋值。不过,由于构造函数是创建一个对象时自动调用的第一个成员函数,因此我们也愿意把构造函数体内的赋值语句当成初始化来看待。
(1)无参构造函数
class Date
{
public:
//无参
Date()
{}
};
如果在类体内没有显式定义构造函数,则编译器在默认生成的缺省构造函数便是如此。
(2)带参构造函数
class Date
{
public:
//带参
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
(3)全缺省构造函数
class Date
{
public:
//带参
Date(int year = 1996, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
全缺省的构造函数不能与 无参的构造函数同时出现在同一个类体内,如果同时出现,在初始化无实参变量时将不知调用它们两个的其中哪个函数。
注意
(1)类的非静态const数据成员和引用成员只能在初始化列表里初始化,因为它们只存在初始化语义,而不存在赋值语义。
(2)类的数据成员初始化可以采用初始化列表或函数体内赋值两种方式,这两种方式的效率不完全相同。
当使用成员初始化列表来初始化数据成员时,这些成员真正的初始化顺序并不一定与你在初始化列表中为它们安排的顺序一致,编译器总是按照它们在类中声明的 次序来初始化的。
1.初始化本类的数据成员 2.在函数体内完成其他的初始化工作。
拷贝构造函数
特征
(1)拷贝构造函数其实是一个构造函数重载;
(2)拷贝构造函数参数必须使用
引用传参,使用
传值方式会引发无穷递归调用;
(3)若为显式定义,系统会给默认缺省的拷贝构造函数,
缺省的拷贝构造函数会依次拷贝类成员进行初始化。
代码呈现
class Date
{
public:
//全缺省构造函数
Date()
{}
Date(const Date& d1)//拷贝构造函数
{
_year = d1.year;
_month = d1.month;
_day = d1.day;
}
private:
int _year;
int _month;
int _day;
};
void Test()
{
Date d1;
Date d2(d1); //与下一行代码等价
Date d3 = d1;
}
但为什么可以直接访问私有成员变量?
1.在类的成员函数中可以
直接访问同类对象的私有/保护成员
2.C++的
访问限定符以类为单位,在这单位内成员可以互相访问
析构函数
定义
当一个对象的声明周期结束时,C++编译器会自动调用一个成员函数,这个特殊的成员函数即析构函数.
特征
(1)析构函数在类名前加上前缀“~”;
(2)析构函数无参无返回值;
(3)一个类有且只有一个析构函数;(构造函数不止一个);
(4)对象生命周期结束时,C++编译系统自动调用析构函数;
(5)析构函数体内并不是删除对象。
代码呈现
class Arry
{
public:
Arry(int size)
{
_ptr = (int *)malloc(size*sizeof(int));
}
//析构函数
~Arry()
{
if (_ptr)
{
free(_ptr);
_ptr = NULL;
}
}
private:
int* _ptr;
};
浅谈对象的构造与析构次序
根据构造与析构函数的解释,可以理解首先是构造函数初始化变量,之后在变量生命周期结束时,将调用析构函数释放内存.但
由于一个类可以存在多个构造函数,却只能有一个析构函数,那么对象在析构的时候如何能够匹配每一个构造函数呢?因为:
(1)析构会严格按照与对象构造相反的次序执行,该次序是唯一的,否则编译器将无法自动执行析构过程;
(2)数据成员的初始化次序完全不受它们在初始化列表中出现次序的影响,只有它们在类中声明的次序决定,因为这个顺序是唯一的。显然,每个构造函数的初始化列表中各成员出现的顺序不可能完全相同,如果数据成员按照初始化列表的次序进行构造,将导致析构函数无法得到唯一的逆序。
成员函数中的this指针
(1)
每个成员函数都有一个指针形参,它的名字是固定的,称为this指针,this指针是隐式的(
构造函数比较特殊,没有隐含this);
(2)编译器会对成员函数进行处理,
在对象调用成员函数时,对象地址作实参传递给成员函数的第一个形参this指针;
(3)this指针是成员函数隐含指针形参,是编译器自己处理的,我们不能在成员函数的形参中添加this指针的参数定义,也不能显式传递对象地址给this。