内存管理
640K ought to be enough for everybody
— Bill Gates 1981
内存分配方式
内存分配方式有三种:
(1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
l 【规则7-2-1】用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。
l 【规则7-2-2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
l 【规则7-2-3】避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。
l 【规则7-2-4】动态内存的申请与释放必须配对,防止内存泄漏。
l 【规则7-2-5】用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。
指针和数组的区别
#include <stdio.h>
void Func(char a[12])
{
printf("%d\n",sizeof(a));
return;
}
int main()
{
char a[] = {"12345 67890"};
printf("%d\n",sizeof(a));//12
Func(a); //4 退化
return 1;
}
指针参数传递内存
#include <stdio.h>
#include <stdlib.h>
//错误,p的副本指向新的地址,p不变,且内存泄露
void GetMemory(char* p,int num)
{
p = (char*)malloc(sizeof(char) * num);
return;
}
//更改p的地址的值,使得p指向新的申请好的地址
void GetMemory2(char** p,int num)
{
*p = (char*)malloc(sizeof(char) * num);
return;
}
//通过函数返回值返回新的地址
char* GetMemory3(int num)
{
char* p = (char*)malloc(sizeof(char) * num);
return p;
}
//确保函数返回时指针指向的空间不会被回收
char* GetString()
{
//p所在的空间在函数返回时会被回收
char p[] = "hello world";
return p;
}
//不算错但是没有意义的函数,注意和上一个函数的区别
char* GetString2()
{
char* p = "hello world";
return p;
}
int main()
{
char* test = NULL;
GetMemory(test,5);
printf("test = %x\n",test);
char* test1 = NULL;
GetMemory2(&test1,5);
printf("test1 = %x\n",test);
char* test2 = GetMemory3(5);
printf("test2 = %x\n",test);
char* test3 = GetString();
printf("test3 = %s\n",test);
char* test4 = GetString2();
printf("test4 = %s\n",test4);
return 1;
}
内存耗尽管理
在用delete释放对象数组时,留意不要丢了符号‘[]’。例如
delete []objects; // 正确的用法
delete objects; // 错误的用法
重载,隐藏和覆盖
#include <iostream>
using namespace std;
void output( int x)
{
cout << " output int " << x << endl ;
}
void output( float x)
{
cout << " output float " << x << endl ;
}
int main()
{
int x = 1;
float y = 1.0;
//output(0.5); // error C2668: “output”: 对重载函数的调用不明确
output(int(0.5)); // output int 0
output(float(0.5)); // output float 0.5
return 1;
}
成员函数的重载、覆盖与隐藏
成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual关键字可有可无。
覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual关键字。
#include <iostream>
class Base
{
public:
virtual void f(float x){ cout << "Base::f(float) " << x << endl; }
void g(float x){ cout << "Base::g(float) " << x << endl; }
void h(float x){ cout << "Base::h(float) " << x << endl; }
};
class Derived : public Base
{
public:
virtual void f(float x){ cout << "Derived::f(float) " << x << endl; } //覆盖
void g(int x){ cout << "Derived::g(int) " << x << endl; } //不满足参数相同 隐藏
void h(float x){ cout << "Derived::h(float) " << x << endl; } //不满足VIRTULE关键字 隐藏
};
void main(void)
{
Derived d;
Base *pb = &d;
Derived *pd = &d;
// Good : behavior depends solely on type of the object
pb->f(3.14f); // Derived::f(float) 3.14
pd->f(3.14f); // Derived::f(float) 3.14
// Bad : behavior depends on type of the pointer
pb->g(3.14f); // Base::g(float) 3.14
pd->g(3.14f); // Derived::g(int) 3 (surprise!)
// Bad : behavior depends on type of the pointer
pb->h(3.14f); // Base::h(float) 3.14 (surprise!)
pd->h(3.14f); // Derived::h(float) 3.14
}
参数的缺省值
正确的示例如下:
void Foo(int x, int y=0, int z=0);
错误的示例如下:
void Foo(int x=0, int y, int z=0);
#include <stdio.h>
void output(int x);
void output(int x,float y = 0.0);
void output(int x)
{
printf("x = %d\n",x);
return;
}
void output(int x,float y)
{
printf("x = %d,y = %f\n",x,y);
return;
}
int main()
{
int x = 1;
float y = 0.5;
output(x);//error C2668: “output”: 对重载函数的调用不明确
output(x,y);
return 1;
}