文章《C++的内存管理》 https://www.cnblogs.com/findumars/p/5929831.html?utm_source=itdadao&utm_medium=referral
以下内容来源与上面的博客,及自己的部分学习。
1,指针是局部变量不需要销毁,但如果指向一块new的堆内存就必须手动delete。只要出现new,就一定会有一个delete相对应。
2,分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
经典面试题之new和malloc的区别:
http://blog.csdn.net/nie19940803/article/details/76358673
3,使用malloc创建的内存一定要free或者delete(如果强转类型使用),因为new还是使用了malloc来创建内存。
malloc在堆上分配的内存块,使用free释放内存,而new所申请的内存则是在自由存储区上,使用delete来释放。《C++的内存管理》刚好写反了。
可以这样来理解,堆是操作系统中的术语,是操作系统所维护的一块特殊内存,所以使用c中的malloc。自由存储区是C++基于new操作符的一个抽象概念,
凡是通过new操作符进行内存申请,该内存即为自由存储区。
4,如果要实现类似new 和 delete的功能,是这样做的:使用操作系统API分配\回收内存,然后显式调用构造函数\析构函数。
5,使用sizeof(ptr)是没有办法拿到指针指向空间的大小的。没办法,只能分配空间的时候专门记录一下。
6,对于多数C++的实现,new[]操作符中的个数参数是数组的大小加上额外的存储对象数目的一些字节。
类重载new[]操作符,类里面定义了一个int类型。打印出来的内存大小为:参数size为:44,表示为4*10+4即,10个int类型+指针apple_ptr 的大小。
即:使用类指针时,分配的内存大小为该类所有变量的内存总值+类变量指针大小。
如果是重载new操作符的话,就不会保存类指针变量。void * Apple::operator new(size_t size)。在内存分配时,应该尽量避免分配对象数组,这样就看省下一个4个字节。
代码如下:
class Apple
{
public:
Apple();
~Apple();
int m_iAppleNum;
void * operator new[](size_t size);
void operator delete[](void *p);
};
Apple::Apple()
{
m_iAppleNum = 100;
}
Apple::~Apple()
{
}
void * Apple::operator new[](size_t size) {
cout << "size of a is: "<< size << endl;
void * p = malloc(size);
return p;
}
void Apple::operator delete[](void *p) {
free(p);
}
int main()
{
Apple * apple_ptr = new Apple[10];
cout << sizeof(apple_ptr) << endl;
system("pause");
return 0;
}
7,常见的内存错误里,在new和delete之后,记得把指针设置为NULL。如果是使用new创建出来的对象,那肯定就是放在堆里面。使用delete销毁。然后设置为NULL。
如下:如果将这个new之后的指针又重新指向一个栈里面的内存,再delete就会直接出错。且,new int(11)这块内存直接泄露了。
int p = 100;
int * i_ptr = new int(11);
i_ptr = &p;
delete i_ptr;
i_ptr = NULL;
正确的写法应该是:
int p = 100;
int * i_ptr = new int(11);
delete i_ptr;
i_ptr = NULL;
i_ptr = &p;
cout << *i_ptr << endl;
i_ptr = NULL;
8,学习中遇到的全局变量,静态变量,常量。
1,C++将在函数中定义的变量命名为局部变量,如果是在main函数中,,在main函数结束时回收。
2,C++将在类中定义的变量命名为成员变量,成员变量存在与对象对应的堆里面。
3,C++将使用extern修饰的变量命名为全局变量,全局变量可以在任何地方声明,只要使用了extern就行,定义只能有一次,如果想使用该全局变量,那必须声明它或者是定义它。全局变量的值可以任意改变。
4,C++将使用#define定义的变量命名为常量,常量定义之后不允许修改,使用常量必须导入相应的头文件。
9,静态全局变量,静态成员变量,静态局部变量。
10,我的理解:C++在做最后工作时,会将所有的类都直接复制到同一个文件中。
10.1,使用:
#ifndef _APPLE_H_
#define _APPLE_H_
#endif
因为在做最后的工作时,C++会将所有的文件合并在一个文件里。C++会从有Main函数的那个文件开始,依次将xx.h文件对应的代码放入。
有这个判断就可以保证不会重复引用。
10.2,在某个类下的函数或者变量只能给当前的类使用,写在main函数文件里的函数可以给所有的类使用,只需要extern声明就好,这说明,
类在这里是一个修饰符,是权限。
11,以10进制,8进制,16进制输出
dec,oct,hex 。 cout<<dec<<dec1<<endl;
12,无名参数, 默认参数
形式参数:可以不用写入值的参数。如:fun(int),fun(int x = 100)
实际参数:必须为参数写入值。如:fun(int x)
无名参数:声明函数时可以包含一个或多个用不到的形式参数。这种情况多出现在用一个通用的函数指针调用多个函数的场合,
其中有些函数不需要函数指针声明中的所有参数。
void test_1(int x,int) 无名参数,第2个参数没有任何作用,但是必须写上。
test_2(int x,int y=20,int z = 30) 默认参数,第2,3个参数不写会使用默认值,20,30.
13,引用变量。引用的符号与取地址是一样的。
1,引用变量只是是一个别名(就好像人名与人外号一样),引用变量就是该变量。
int b = 100;
int & a_1 = b;
2,引用变量做为函数的形参时可以不初始化形参,由调用该函数时由实参初始化。避免了传递参数时的消耗。
void fun(int& a) a++;
3,由于2的原因,所以经常使用const与&一起使用,避免参数数据很大时创建的局部变量,但是参数不再可以改变。以前的C语言中函数参数传递是值传递,
如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为 这样可以避免将整块数据全部压栈,可以提高程序的效率。如果传递的是对象,还将调用拷贝构造函数。
void fun(const int& a)
4,引用变量必须初试化,除非引用变量作为外部变量或者作为类的成员时由构造函数初始化。
如:???
5,返回局部变量的引用是被允许的操作,这样可能带来的问题???,返回一个局部变量的引用时不会创建临时变量。
6,数组不存在引用,因为数组名是一个地址,而引用是一个标识,标识该变量的值是多少,所以没有数组引用。
7,函数返回一个局部变量的值,它是一个常量。“xx”字符串也是常量,都需要使用引用常量的形式操作。引用型参数应该在能被定义为const的情况下,尽量定义为const。
8,使用引用达到多态的目的,父类的引用指向子类对象时,通过虚函数实现多态效果,(猜测:因为地址结构一样,所以可以直接使用)
14,常量
1,100(10进制),0144(8进制),0x64(16进制) , 100(int),100u(unsigned int),100l(long),100ul(unsigned long),1.2e10,1.2e-10(1.2*10^-10),
wchar_t wc[100] =L"xxx"(宽字符的表示)。
常量的定义可以是,字符串,分号。
#define FENHAO ;
#define TEST "xxxx"
const k = 100;有些编译器默认为int类型,有些会直接报错,需要设置。
15,输入。
1,使用cin输入多个整数时,使用空格,tab,换行,可以达到为多个变量输入数据的目的。
如: int a,b; cin >> a>>b;
2,使用头文件<string>里的getline(cin,xxxx)函数输入有空格的字符串。
如: string mystr=""; getline(cin, mystr);
3,使用头文件<sstream>将字符串转化为int值。
如: string mystr="";
getline(cin, mystr);
int strToint =0 ;
stringstream(mystr)>>strToint;
strToint++;
或者,自己写一个也行。
由于对string类不太了解,所以分开了2个函数分别处理char[]字符串和string。
注意:char[]数组与string没有值的结尾的地方默认为空字符,也就是0或者‘\0’.
string mystr="1203";
char c_[100] = "123";
cout<<string_to_int(mystr)<<endl;
cout<<char_to_int(c_)<<endl;
int tmp = 1;
int value_end = 0;
int i = 0;
while(!(inPtr[i]==0)){
i++;
}
i--;
while(i>=0){
value_end += ((int)inPtr[i] - 48)*tmp;
tmp*=10;
i--;
}
return value_end;
}
int string_to_int(string strPtr){
int tmp = 1;
int value_end = 0;
int i = 0;
while(!(strPtr[i]==0)){
i++;
}
i--;
while(i>=0){
value_end += ((int)strPtr[i] - 48)*tmp;
tmp*=10;
i--;
}
return value_end;
}
16,循环
for循环
int i = 0,j = 100;
for (; i < j; )
{
i++,j--;
printf("%d\n",i);
}
17,函数。
inline相当于一个宏定义,只是定义的是一个函数结构而已,调用该函数的地方,会默认变成代码。
inline int test_(int x){}
test_(1);
18,数组。
1,数组的长度是确定且固定不变的。
2,全局数组默认会初始化为0,局部数组默认没有初始化 。应该是局部数组随时都可能产生,所以系统没有给它初始化。
3,多维数组就是一维数组。它们在内存中排列是一样的。
4,数组做参数,传递的是地址,一个函数无法将一块连续内存的数据作为参数传递。
定义多维数组参数时,后面的维度必须是确定的。使用(&arr)+1,拿到的是整个数组长度的值。
void arr_(int arr[][2]){}
19.字符串。
1,因为字符串经常都是有空余的情况,所以规定字符串末尾处为一个空字符,即:0或者‘\0’。
2,在初始化字符串时,如果使用这种方式,必须在末尾手动加上空字符。
char str[] ={'1','2','3','\0'};
3,为什么数组只能在初始化时统一赋值????
4,使用以下方式输入固定长度的字符串。多次输出直接覆盖。
char str[100];
cin.getline(str,10);
5,使用atoi,atof,atol。将char xx[]数组值转化为int,float,long类型。自己实现 atoi,atof,atol 。
至于string类型,因为string本身是一个类,所以我猜,string本身就有相应的转化函数。
注意:空格字符(' '的ascii值为32)与空字符('\0'的ascii值为0)与0字符('0'的ascill值为48)是不一样的。
char str[100];
cin.getline(str,100);
long value = atol_1(str)+1;
cout<<value<<endl;
int atoi_1(const char* ch_ptr){
if(*ch_ptr==0)//空字符
return 0;
while(*ch_ptr==' '){//空格字符
ch_ptr++;
}
int flag = 1;
if(*ch_ptr=='-'){//符号字符
flag = -1;
ch_ptr++;
}
int value = 0;
while((*ch_ptr)>='0'&&(*ch_ptr)<='9'){
value = value*10 + *ch_ptr -'0';
ch_ptr++;
if(value<0)//值太大溢出,超过了int的界限
{
printf(" int over \n");
return 0;
}
}
return value*flag;
}
float atof_1(const char* ch_ptr){
if(*ch_ptr==0)
return 0;
while(*ch_ptr==' '){//空格字符
ch_ptr++;
}
int flag = 1;
if(*ch_ptr=='-'){
flag = -1;
ch_ptr++;
}
float value_front = 0;
float value_back = 0;
bool b_point = false;
float tmp = 0.1;
while ((*ch_ptr>='0'&&*ch_ptr<='9')||*ch_ptr=='.')
{
if(*ch_ptr=='.'){
b_point = true;
}else{
if(b_point==false){
value_front = value_front*10 + *ch_ptr-'0';
}else{
value_back = value_back + (*ch_ptr-'0')*tmp;
tmp*=0.1;
}
}
ch_ptr++;
}
return (value_front+value_back)*flag;
}
long atol_1(const char* ch_ptr){
if(*ch_ptr==0)
return 0;
while(*ch_ptr==' '){
ch_ptr++;
}
int flag = 1;
if(*ch_ptr=='-'){
flag = -1;
ch_ptr++;
}
long value = 0;
while(*ch_ptr>='0'&& *ch_ptr<='9'){
value = value*10 + (*ch_ptr-'0');
ch_ptr++;
}
return value * flag;
}
6,几个字符串操作函数。
判断2个字符串是否一样。
int strcmp_1(const char* in_ptr,const char* dest_ptr){
if(*in_ptr==0&&*dest_ptr==0)
return 1;
while(*in_ptr==*dest_ptr){
if(*in_ptr==0){
return 1;
}
in_ptr++;
dest_ptr++;
}
return 0;
}
20,指针。
1,数组名是一个指针常量。
2,指针的加减操作改变的地址大小是该指针的类型。
3,函数指针。????
int test_add(int x,int y){
return x+y;
}
int (*add_fun)(int,int);
add_fun = test_add;
cout<<add_fun(1,2)<<endl;
4,内存分配。
malloc分配的内存不会初始化,calloc会初始化为0 。calloc传递2个参数,1是某种类型长度,2是某种类型。malloc参数为长度。
int* int_ptr = (int*)malloc(sizeof(int)*5);
int* int_ptr2 = (int*)calloc(5,sizeof(int));
int* int_ptr3 = (int *)realloc(int_ptr,sizeof(int)*10);
free只能释放alloc相关函数创建的内存块。
free(int_ptr);
free(int_ptr2);
free(int_ptr3);//error,重复释放内存块
实现malloc,remalloc,free。
http://blog.codinglabs.org/articles/a-malloc-tutorial.html
21,结构体。
1,结构体中定义指针,指针需要单独开辟内存空间。
typedef struct move{
int move_id;
fruit f_;
fruit* f_ptr;
}mov1,mov2;
m_v.f_ptr = (fruit*)malloc(sizeof(fruit));
free(m_v.f_ptr);
2,结构体指针。
22,自定义类型。
1,自定义一个char[10]类型。
typedef char char_arr_10[10];
char_arr_10 c_;
memset(c_,0,sizeof(char_arr_10));
cout<<sizeof(char_arr_10)<<endl;
2,union 联合,
1,联合中的变量使用同一快内存,改变其中一个变量等于修改其他变量(就是 2进制数按照什么样的方式表现,是float还是int)。
2,变量大小取决于最大的那个变量。(就是 2进制下,小的变量高位舍弃)。
union some{
short sho;//2byte
int i;//4byte
char c[4];//4byte
struct s{short one;short two;}s_;//4byte
}tmpOne;
3,匿名联合。联合不加对象名就是匿名联合。另外,struct和union是可以省略相应名字不写的。
struct{
int i;
union{short s1;short s2;};
}s;
s.s1 = 100;
cout<<s.s2;
11,类。
1,在类外部实现函数,使用类名+::符号,告诉编译器,这个函数是这个类的,该函数的成员可以任意使用。
如果在类中实现函数,效果,使用和外部函数一样,区别在于编译器将类内部实现的函数设置为inline。
2,如果构造,析构函数为private,那你没有办法创建对象,函数内存在成员为指针时,在构造函数里开辟空间,在析构里delete。
构造函数默认有2个,一个为空,一个为拷贝构造函数。
CExample::CExample (const CExample& rv) {
a=rv.a; b=rv.b; c=rv.c;
}
1,重载加减操作符。可以把操作符理解成函数,a+b就是a(b),将b当成参数。
Fruit f1(1); Fruit f2(2);
Fruit f3 =f1+f2;
Fruit f4 = f1.operator+(f2);
Fruit f5 = f1-f2;
Fruit.h
Fruit operator+(Fruit fruit){
Fruit f;
f.a = a + fruit.a;
return f;
};
Fruit operator-(Fruit fruit);
Fruit Fruit::operator-(Fruit fruit){
Fruit f;
f.a = a-fruit.a;
return f;
}
2,this指针,每个对象都拥有的指向自身的指针。
=操作符可能的实现。
Fruit operator=(Fruit fruit);
Fruit Fruit::operator=(Fruit fruit){
a = fruit.a;
return *this;
}
使用对象或者类都可以操作静态变量和函数:f1.getObjCount(),Fruit::getObjCount()。
1,int Fruit::st_i = 0;静态成员变量需要在类外部初试话,改变量会被每个对象都声明一次,这也许就是为什么类成员没有初试化的一个原因。
2,不要刻意去区分静态成员与全局变量的区别。了解它们为什么而存在。
1,尽量不要使用友元函数,它是一种破坏面向对象结构的存在。可以说它是面向对象之外的方法。
friend int getC(Fruit fruit);
int getC(Fruit fruit){
return fruit.c;
}
2,友元关系是单向的。友元类与友元函数一样。
friend class Apple;