一、本文基础概念与结论:
面向对象:
程序 = 对象+对象+对象+…+对象之间消息通信
是什么样的对象之间可以通信呢:
对象A和对象B他们所属的类和类之间要有关系。
类之间关系基本分类:
组合(聚合):类A是类B的一部分,类A定义的对象作为了类B的成员变量;
继承:类A是类B的一种,类A继承自类B。
构造一个对象编译器做了哪些事情:
1.根据对象的类型,系统给对象分配内存;
2.构造当前对象的成员对象;
3.根据对象的定义方式,调用相应的构造函数。
初始化列表用在哪里:
构造函数中,包括构造函数和拷贝构造函数中。
二、聚合关系与构造函数的初始化列表:
//这里实现一个date日期类 和 stu学生类,其中date实例的对象作为stu的成员变量
//成员对象的初始化 在 stu对象的初始化 之前,所以我们不能在stu构造函数指定date对象初始化方式
//所以我们在 stu 的初始化列表中进行初始化
//日期类
class date
{
public:
date(int y,int m,int d)
{
year = y;
month = m;
day = d;
}
void show()
{
cout << year << " " << month << " " << day << " " << endl;
}
private:
int year;
int month;
int day;
};
//学生类
class stu{
public:
stu(char *n, int a, float s ,int y, int m, int d)
:age(a), score(s), date(y,m,d) //构造函数的初始化列表: 在这里进行成员对象 的 初始化
{
strcpy(name,n);
//age = a;
//score = s;
//date(y,m,n); 不能在这里初始化,因为成员对象的初始化在stu对象初始化之前。
}
void show()
{
cout << "姓名:" << name;
cout << "年龄:" << age;
cout << "成绩:" << score<<" ";
date.show(); //调用成员对象 date 的show()方法
}
private:
char name[20];
int age;
float score;
date date; //成员对象
};
简单的测试一下:
int main()
{
stu s("king",10,100,1997,9,9);
s.show();
return 0;
}
执行结果:
姓名:king年龄:10成绩:100 1997 9 9
请按任意键继续. . .
三、构造函数的初始列表使用的注意事项:
1.只能用在构造函数中,当然包括拷贝构造函数;
2.初始化列表的先后顺序与成员变量的定义先后顺序有关,与在初始化列表出现的先后顺序无关。
//ex: 一个错误理解
class test
{
public:
test(int data = 100)
:mb(data), ma(mb) //依然是先用mb初始ma,再用data初始化mb,因为ma先定义
{
//
}
void show()
{
cout << "ma: " << ma << endl;
cout << "mb: " << mb << endl;
}
private:
int ma;
int mb;
};
1.ma先定义,所以初始化列表中肯定会先初始化ma,
此时mb还没右初始化,所以mb的值可能是0xcccccccc,一些无效值;
2.再用data初始化mb
验证一下:
int main()
{
test t1 = test();
t1.show();
return 0;
}
执行结果:
ma: -858993460
mb: 100
请按任意键继续. . .
3.在初始化列表中和构造函数区别:
构造函数中属于赋值操作;
初始化列表中属于初始化的过程,对于成员对象来说,用初始化的方式构造成员变量和赋值的方式得到的我们想要的对象,效率上初始化的效率更高。
并且如果我们进行赋值的方式,前提是要有默认的构造函数,否则会出错。
4.常量和引用必须放在构造函数的初始化列表中:
c++中常量必须初始化;
引用必须初始化;
//情景1 : 成员变量 有 引用
class test
{
public:
test(int data = 100) :mb(data), ma(data)
{
//
}
void show()
{
cout << "ma: " << ma << endl;
cout << "mb: " << mb << endl;
}
private:
int &ma;
//ma保存的是构造函数data的值,是构造函数栈帧上的数值,构造函数执行完成栈帧将被回退
int mb;
};
int main()
{
test t1 = test();
t1.show();
return 0;
}
执行结果:
ma: 18893946
mb: 100
请按任意键继续. . .
引用ma保存的内存data 对应的函数栈帧被回退,
所以在show函数中访问ma内存会访问无效值,可以认为show函数将构造函数的栈帧覆盖了。
使用引用注意的第二个点:成员变量有引用,将不产生默认的赋值运算符重载函数
int main()
{
test t1 = test();
t1.show();
test t2;
t2 = t1; //赋值
t2.show();
return 0;
}
执行结果:
error C2582: “operator =”函数在“test”中不可用
说明:
在这种情况下,编译器不能产生默认的赋值运算符重载函数;
原因:
因为引用一经使用就不能被其他引用
test t2; //t2对象已经引用了一块内存
t2 = t1; //这里相当于用t2的ma引用再去 引用其他内存的引用 ,
//引用一旦引用一块内存,就不能去引用别的内存,所以会出错
既然编译器不能给我们默认生成赋值运算符重载函数,我自定义实现一个发现编译通过了:
void operator=(const test &src)
{
ma = src.ma;
mb = src.mb;
}
这是为什么:
这是因为我们在使用引用的时候,访问的是对应地址中的数据,而不是地址。所以实际上是内存中的值进行了赋值。
执行结果:
ma: 13132090
mb: 100
ma: 13132120
mb: 100
请按任意键继续. . .
回顾下引用:引用保存的是数据的地址,但是访问引用时自动解引用使用内存中的值。
成员变量中有常量的情况:
class test
{
public:
test(int data = 100) :mb(data), ma(data)
{
//我们不能在这里对ma赋值,一定要在初始化列表进行初始化
//因为ma是常量
}
void show()
{
cout << "ma: " << ma << endl;
cout << "mb: " << mb << endl;
}
private:
const int ma;
int mb;
};
四、指向成员的指针
class test
{
public:
test(int data = 100) : ma(data)
{
//
}
void show()
{
cout << "test::show(): " << ma << endl;
}
int ma;
};
void _cdecl show()
{
cout << "global::show() " << endl;
}
int main()
{
test t1;
test *t2 = new test;
//定义全局函数的函数指针
void(*pf)() = show;
pf();
//定义test类中的函数指针
void(test::*ppf)() = &test::show;
//调用的时候依赖对象调用
//1.依赖对象调用 .*
(t1.*ppf)();
//2.依赖指向对象的指针 ->*
(t2->*ppf)();
delete t2;
return 0;
}
指针调用函数和对象调用的函数的区别:
1.类中实现的函数被编译器定义为内联函数,而内联函数在编译的时候会在函数调用点自动展开,通过对象调用的函数效率高;
2.使用指针调用函数,编译的时候不知道调用哪个函数,因为指针保存的是函数的入口地址,不能被编译器的处理成内联函数,效率较低。