1:数据成员必须在构造函数初始化列表中初始化:
-
没有默认构造函数的内嵌对象 <基类没有构造函数>
-
引用类型的数据成员 int &rx
-
常数据成员 <const 修饰的变量>
-
成员类型是没有默认构造函数的类。
2:深拷贝和浅拷贝
-
c++默认的拷贝构造函数是浅拷贝 浅拷贝就是对象的数据成员之间的简单赋值,如你设计了一个没有类而没有提供它的复制构造函数,当用该类的一个对象去给令一个对象赋值时所执行的过程就是浅拷贝
-
class A { public: A(int _size) : size(_size) { data = new int[size]; } // 假如其中有一段动态分配的内存 A(){}; ~A() { delete [] data; } // 析构时释放资源 private: int* data; int size; } int main() { A a(5), b = a; // 注意这一句 } // err // 这里b的指针data和a的指针指向了堆上的同一块内存,a和b析构时,b先把其data指向的动态分配的内存释放了一次,而后a析构时又将这块已经被释放过的内存再释放一次。对同一块动态内存执行2次以上释放的结果是未定义的,所以这将导致内存泄露或程序崩溃。
-
深拷贝
-
所以这里就需要深拷贝来解决这个问题,深拷贝指的就是当拷贝对象中有对其他资源(如堆、文件、系统等)的引用时(引用可以是指针或引用)时,对象的另开辟一块新的资源,而不再对拷贝对象中有对其他资源的引用的指针或引用进行单纯的赋值
-
class A { public: A(int _size) : size(_size) { data = new int[size]; } // 假如其中有一段动态分配的内存 A(){}; A(const A& _A) : size(_A.size) { data = new int[size]; } // 深拷贝 ~A() { delete [] data; } // 析构时释放资源 private: int* data; int size; } int main() { A a(5), b = a; // 这次就没问题了 }
-
区别:
-
深拷贝和浅拷贝的区别是在对象状态中包含其它对象的引用的时候,当拷贝一个对象时,如果需要拷贝这个对象引用的对象,则是深拷贝,否则是浅拷贝。
-
或者定义的类成员中含有, 指针变量。
-
// 1 在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的;但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象,所以,此时,必须采用深拷贝。 // 2 深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。
3:内敛函数
3.1 内敛函数的特点:
- 1:
内敛函数一般规模较小,程序再调用这些成员函数时,不是真正的执行函数的调用过程 ,不需要函数调⽤时候的压栈,跳转,返回的开销。以理解为内联函数是以空间换时间
- 2:
函数代码嵌入程序的调用点(内敛函数在源文件中不是调用而是按原样展开),这样可以大大减少调用成员函数的时间
- 3:
事实上我们可以用内联函数完全取代预处理宏
3.2 内敛函数的定义:
-
关键字:Inline
-
内联函数必须是和函数体申明在一起,才有效
Inline Tablefunction(int I);
// 无效
================================================================================
Inline tablefunction(int I) {return I*I};
// 编译器只是把函数作为普通的函数申明,我们必须定义函数体
3.3 内敛函数的应用
- 1:
定义类成员变量的存取函数
- 2:
实现宏的效果
原因:宏在使用时,不进行参数检查,容易出现二义性
// 计算平方差
#define TABLE_MULTI(x) ((x)*(x))
==========================================
inline int square(int x){ return x*x;}; // 避免了二义性
3.4 内敛函数的局限性
就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数
原因:内联函数就和普通函数执行效率一样了。
3.5 函数、宏、内联函数的比较
-
1: 普通函数:
函数的调用必须要将程序执行的顺序转移到函数所存放在内存中的某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方
-
缺点:
函数调用要有一定的时间和空间方面的开销,于是将影响其效率
-
2: 宏:
而宏只是在预处理的地方把代码展开,不需要额外的空间和时间方面的开销,所以调用一个宏比调用一个函数更有效率。
-
缺点:
(1):宏不能访问对象的私有成员。 (2) 宏的定义很容易产生二意性。
-
3: 内敛函数:
内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。
-
内敛函数是真正的函数。
4:new 和 malloc 函数
4.1:new 和 malloc 函数
-
堆是操作系统维护的一块内存,是一个物理概念**
堆堆(heap)是C语言和操作系统的术语,堆是操作系统所维护的一块特殊内存,它提供了动态分配的功能,使用malloc()、free()来申请/释放内存。
-
自由存储是C++中通过new与delete动态分配和释放的对象的存储区,是一个逻辑概念。
自由存储区而自由存储是C++中通过new和delete动态分配和释放对象的抽象概念。基本上,所有的C++编译器默认使用堆来实现自由存储。也就是说,默认的全局运算符new和delete也许会使用malloc和free的方式申请和释放存储空间,也就是说自由存储区就位于堆上
4.2:C++ 中的内存分布
堆, 栈, 全局/静态存储区, 常量存储区, 自由存储区
4.3: new 和 malloc 的区别
函数类型 | 属性 | 参数 | 返回类型 | 分配失败 | 自定义类型 |
---|---|---|---|---|---|
new | C++关键字需要编译器支持 | 申请内存分配时<无须指定内存块的大小,编译器会根据类型信息自行计算 | 分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符 | 分配失败时,会抛出bac_alloc异常 | new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现) |
malloc | 库函数需要头文件支持 | 显式地指出所需内存的尺寸 | 分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型 | 分配内存失败时返回NULL | 只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。 |
函数类型 | 重载 | 内存区域 |
---|---|---|
new | 允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址,(布局new, 不位于堆中,) | new操作符从自由存储区(free store)上为对象动态分配内存空间。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区 |
malloc | 不允许重载 | malloc函数从堆上动态分配内存。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中 |
4.4: new 一个数组
**对于数据C++定义new[]专门进行动态数组分配,用delete[]进行销毁。new[]会一次分配内存,然后多次调用构造函数;delete[]会先多次调用析构函数,然后一次性释放。**
- malloc 优点:
如果在使用过程中发现内存不足,可以使用realloc函数进行内存重新分配实现内存的扩充。realloc先判断当前的指针所指内存是否有足够的连续空间,如果有,原地扩大可分配的内存地址,并且返回原来的地址指针;如果空间不够,先按照新指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来的内存区域。 new没有这样直观的配套设施来扩充内存。
5: static 的用法
5.1: 静态局部变量:
- 用于函数体内部修饰变量,这种变量的生存期长于该函数。
int foo(){
static int i = 1; // note:1
//int i = 1; // note:2
i += 1;
return i;
}
// 静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值。
内存分布
1.栈区: 由编译器自动分配释放,像局部变量,函数参数,都是在栈区。会随着作用于退出而释放空间。
3.堆区:程序员分配并释放的区域,像malloc©,new(c++)
3.全局数据区(静态区):全局变量和静态便令的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束释放。
4.代码区
特点
(1)该变量在全局数据区分配内存(局部变量在栈区分配内存);
(2)静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化(局部变量每次函数调用都会被初始化);
(3)静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0(局部变量不会被初始化);
(4)它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,也就是不能在函数体外面使用它(局部变量在栈区,在函数结束后立即释放内存);
5.2:静态全局变量:
定义在函数体外,用于修饰全局变量,表示该变量只在本文件可见。
//file a.c
//static int n = 15; //note:5
int n = 15; //note:6
//file b.c
#include <stdio.h>
extern int n;
void fn()
{
n++;
printf("after: %d\n",n);
}
void main()
{
printf("before: %d\n",n);
fn();
}
特点
- 静态全局变量不能被其它文件所用(全局变量可以);
- 其它文件中可以定义相同名字的变量,不会发生冲突(自然了,因为static隔离了文件,其它文件使用相 同的名字的变量,也跟它没关系了);
5.3: 静态函数:
准确的说,静态函数跟静态全局变量的作用类似:
//file a.c
#include <stdio.h>
// static void fn() // 之后通过extern 也不能访问到函数, 因为static 进行了隔离
void fn()
{
printf("this is non-static func in a");
}
//file b.c
#include <stdio.h>
extern void fn(); //我们用extern声明其他文件的fn(),供本文件使用。
void main()
{
fn();
}
静态函数和静态全局的优点
1.静态函数不能被其它文件所用;
2.其它文件中可以定义相同名字的函数,不会发生冲突;
5.4 :静态 class 中的数据成员
- .静态数据成员:用于修饰 class 的数据成员,即所谓“静态成员”。这种数据成员的生存期大于 class 的对象(实体 instance)。静态数据成员是每个 class 有一份,普通数据成员是每个 instance 有一份,因此静态数据成员也叫做类变量,而普通数据成员也叫做实例变量。
#include<iostream>
using namespace std;
class Rectangle
{
private:
int m_w,m_h;
static int s_sum;
public:
Rectangle(int w,int h)
{
this->m_w = w;
this->m_h = h;
s_sum += (this->m_w * this->m_h);
}
void GetSum()
{
cout<<"sum = "<<s_sum<<endl;
}
};
int Rectangle::s_sum = 0; //初始化
int main()
{
cout<<"sizeof(Rectangle)="<<sizeof(Rectangle)<<endl;
Rectangle *rect1 = new Rectangle(3,4);
rect1->GetSum();
cout<<"sizeof(rect1)="<<sizeof(*rect1)<<endl;
Rectangle rect2(2,3);
rect2.GetSum();
cout<<"sizeof(rect2)="<<sizeof(rect2)<<endl;
system("pause");
return 0;
}
// 输出
/*
sizeof(Rectangle)=8
12
sizeof(Rectangle)=8
18
sizeof(Rectangle)=
*/
说明:
- 对于非静态数据成员,每个类对象(实例)都有自己的拷贝。而静态数据成员被当作是类的成员,由该类型的所有对象共享访问,对该类的多个对象来说,静态数据成员只分配一次内存。
- 静态数据成员存储在全局数据区。静态数据成员定义时要分配空间,所以不能在类声明中定义, 而是在全局中提前进行定义。
5.5 : **静态成员函数:**用于修饰 class 的成员函数。
#include<iostream>
using namespace std;
class Rectangle
{
private:
int m_w,m_h;
static int s_sum;
public:
Rectangle(int w,int h)
{
this->m_w = w;
this->m_h = h;
s_sum += (this->m_w * this->m_h);
}
static void GetSum() //这里加上static
{
cout<<"sum = "<<s_sum<<endl;
}
};
int Rectangle::s_sum = 0; //初始化
int main()
{
cout<<"sizeof(Rectangle)="<<sizeof(Rectangle)<<endl;
Rectangle *rect1 = new Rectangle(3,4);
rect1->GetSum();
cout<<"sizeof(rect1)="<<sizeof(*rect1)<<endl;
Rectangle rect2(2,3);
rect2.GetSum(); //可以用对象名.函数名访问
cout<<"sizeof(rect2)="<<sizeof(rect2)<<endl;
Rectangle::GetSum(); //也可以可以用类名::函数名访问
system("pause");
return 0;
}
== class (类中), 静态成员函数的优点:
1.静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
2.非静态成员函数可以任意地访问静态成员函数和静态数据成员;
3.静态成员函数不能访问非静态成员函数和非静态数据成员;
4.调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,也可以用类名::函数名调用(因为他本来就是属于类的,用类名调用很正常)
- 补充
静态成员函数不能访问非静态成员函数原因:静态成员函数是属于类的, 而对类的成员一无所知, 而类的对象对静态成员函数和类中的其他成员都可以调用。