四、static类成员:
假设我们使用C++来开发一款游戏没有如下的场景:
假设有一个具有火星人和其他人物的视频。每个火星人都很勇敢,而且当火星人注意到至少存在五个火星人的时候,它总是袭击其他空间的生物。如果少于五个人的话,则每个火星人非常胆小。所以每个火星人都要知道现在游戏中有多少个火星人。 |
我们现在考虑解决这个问题。如何定义变量,表示游戏中有多少火星人呢?
1.使用全局变量:
不是一个很好的主意,一般在编程规范中,都是建议编程人员少使用全局变量,因为这样会增加代码的耦合度
2.每个对象都维护一个count?
这样会使占用的内存增加,维护的复杂程度增加。
3.C++提供static(静态)数据成员
Static数据成员和成员函数是属于类层次的,不属于某一个对象。为了封装替代使用全局变量的使用环境。
(1)在static成员函数中不能使用this指针。
(2)即使没有实例化的对象,static数据成员和成员函数仍然可以使用。
(3)static成员的名字在类的作用域中,因此可以避免与其他类的成员或者全局对象名字冲突。
(4)可以实施封装,static成员可以是私有成员,而全局对象则不可以。
(5)通过阅读代码很容易看出static成员是与特定的类相关联的,清晰的显示程序员的意图。
五.动态内存分配:
1.C语言的动态内存分配:
malloc/free函数:用于动态的申请内存和释放内存,申请的空间在堆上。
2.内存区域:
全局变量、静态数据、常量 | 数据区 |
所有的类成员函数和非成员函数代码 | 代码区 |
为运行函数而分配的局部变量,函数参数, 返回值,返回地址等…… | 栈区 |
动态内存分配区 | 堆区 |
C++的运算符:new/delete
在堆上生成对象,需要调用构造函数
在堆上生成的对象,在释放的时候需要自动调用析构函数
new/delete,malloc/free需要配对使用
new[ ]/delete[ ]生成和释放对象数组
new[ ]/delete[ ]是运算符,而malloc/free是函数调用。
3.代码放在代码区,数据则根据类型的不同,存放在不同的区域。
Bss段 存放没有初始化或者初始化为0的全局变量
大多数的操作系统在加载程序的时候会吧bss段全局变量清零。为了保证程序的可移植性,最好手工把变量初始化为0。
在程序运行周期内,bss数据是一直都在的
同样的,作为全局变量,在整个的运行周期内,data数据一直都存在
静态变量在第一次进入作用域时候被初始化,以后不必再被初始化
静态成员变量在类之间共享数据,也是放在全局/静态数据区中,并且只有一份拷贝。
rodata存放常量数据(只读数据)
常量不一定放在rodata中,有些直接和指令代码放在一起,放在text中。
字符串常量,编译器会去掉重复的字符串,保证只有一个副本。
常量是不能修改的。
字符串常量会被编译器自动放到rodata中,加const关键字修饰的全局变量也放在rodata中。
栈中存储自动变量或者局部变量,以及传递的参数等。
在一个函数内部定义了一个变量,或者向函数传递参数时,这些变量和参数存储在栈上,当变量退出这些变量的作用域时,这些栈上的存储单元会被自动释放。
对象的生命周期是指对象从创建到被销毁的过程,创建对象时要占用一定的内存,因此升格程序占用的内存随着对象的创建和销毁动态的发生变化。
变量的作用域决定了对象的生命周期
全局对象在main之前被创建,main退出后被销毁。
静态对象和全局对象相类似,第一次进入作用域被创建,但是程序开始时,内存已经分配好。
作用域由{}定义,并不一定是整个函数。
通过new创建的对象,很容易造成内存泄漏,通过new创建的对象一直存在,直到被delete销毁。
隐藏在中间的临时变量的创建和销毁,生命周期很短,容易造成问题。(拷贝构造函数)
六、拷贝构造函数:
1.通过一个对象构造另外一个对象时候需要调用的构造函数。
C++会默认调用的函数由:构造函数,析构函数,拷贝构造函数,等号运算符承载函数
拷贝构造函数是一种特殊的构造函数,具有单个形参,此形参是对该类型的引用。当定义一个新的对象并用一个同类型的对象对它进行初始化时候,将显示使用拷贝构造函数。
当将该类型的对象传递给函数或从函数返回该类型的对象时,将隐式的调用拷贝构造函数。
如果一个类没有定义拷贝构造函数,编译器会默认的提供拷贝构造函数。
2.拷贝构造函数的形式:名字和类名相同(const 类名&)
(1)用一个对象构造另外一个对象的时候
(2)拷贝一个临时对象的时候(函数调用和返回的时候)。
3.编译器提供的拷贝构造函数的行为:
(1)执行逐个成员的初始化,将新对象初始化为原对象的副本。
(2)“逐个成员”,指的是编译器将现有的独享的每个非static成员,依此复制到正在创建的对象。
4.为什么C++要定义拷贝构造函数:
浅拷贝:
深拷贝:
进行深拷贝的时候需要注意的:
(1)防止自赋值。(会造成死递归)
(2)防止内存泄漏。(释放原来的空间)
何时需要定义拷贝构造函数:
类数据成员有指针
类数据成员管理资源
如果一个类需要析构函数来释放资源,则它也需要一个拷贝构造函数
如果想禁止一个类的拷贝构造,需要将拷贝构造函数声明为private。
七、const关键字:
1.C++提供了const限定符:指定一个不该被改动的对象。
2.const限定指针的类型:
Const出现在*左边,表示被指物是常量。
Const出现在*右边,表示指针自身是常量。
3.const的数据成员必须使用初始化列表进行初始化。
Const成员函数 :类的接口清晰,确定哪些函数可以修改数据成员
4.使用pass-by-reference-to-const(const引用)替换pass-by-value(按值传递);控制使用指针和引用传递的实参被以外的修改。省去了一个对象的拷贝构造过程和析构过程,效率会高一些。下面两个函数在语义上是一样的,都表示不会对person的对象进行修改。但是bar函数的效率会高一些。省去了拷贝构造函数和析构函数。
Void foo(Person person)
{
}
Void bar(const Person&person)
{
}
八、友元函数和友元类:
1.情况下,允许特定的非成员函数访问一个类的私有成员,但同时仍然组织一般的访问。
2.友元机制允许一个类将对其非公有成员的访问权限授予制定的函数或者类。
(1)友元的声明以关键字friend开始。
(2)只能出现在类定义的内部。
(3)可以出现在类中的任何地方,不是授予友元关系的那个类成员,所以不受其声明出现部分的访问控制的影响。
3.友元关系是授予的:为了让B类成为A类的友元,类A必须显示的声明类B是它的友元。
4.友元关系是不对称的 :如果类A是类B的友元,类B是类C的有元,不能推出类B是类A的友元。
5.友元会破坏封装。
6.友元的实例: