1、new 和 delete 基本语法
1 )在软件项目开发过程中,我们经常需要动态地分配和撤销内存空间,特别是数据结构中结点的插入与删除。在 C 语言中是利用库函数 malloc 和 free 来分配和撤销内存空间的。C++ 提供了较简便而功能较强的运算符 new 和 delete 来取代 malloc 和 free 函数。( 注意: new 和 delete 是运算符,但也是C++里边的关键字,但不是函数,因此执行效率高。 )
2 )虽然为了与 C 语言兼容, C++ 仍保留 malloc 和 free 函数,但建议用户不用 malloc 和 free 函数,而用 new 和 delete 运算符。 new 运算符的例子 :new int; // 开辟一个存放整数的存储空间,返回一个指向该存储空间的地址 ( 即指针 )new int(10); // 开辟一个存放整数的空间,并指定该整数的初值为 10 ,返回一个指向该存储空间的地址new char[100]; // 开辟一个存放字符数组 ( 包括 100 个元素 ) 的空间,返回首元素的地址new int[5][4]; // 开辟一个存放二维整型数组 ( 大小为 5*4) 的空间,返回首元素的地址float *p=new float (3.14159); // 开辟一个存放单精度数的空间,并指定该实数的初值为3.14159 ,将返回的该空间的地址赋给指针变量 p
new 运算符动态分配堆内存
指针变量 = new 类型 ( 常量) ;指针变量 = new 类型 [ 表达式 ];
指针变量 = new 类型 [ 表达式 ][ 表达式 ] // 二维数组
创建数组对象时,不能为对象指定初始值
普通类型(非数组)使用 : delete 指针变量 ;
delete[] 指针变量 ;
#include <stdlib.h>
#include <iostream>
using namespace std;
//分配基础类型
int main(void) {
int* p1 = new int;//第一种分配动态内存不执行初始化
*p1 = 100;
int* p2 = new int(100);//第二种分配动态内存同时执行初始化
int* p3 = (int*)malloc(sizeof(int));// 第三种 malloc 返回值是 void *
free(p1); //基础类型可以 new h和 free 可以混搭
delete p3; //基础类型可以 malloc delete 可以混搭
delete p2; //free(p2); 同样效果
system("pause");
return 0;
}
正常编译正常运行,就是对于申请普通变量时,free和new可以混搭,及new申请的普通变量可以通过free释放。delete和malloc也可以混搭。
#include <stdlib.h>
#include <iostream>
//分配数组变量
int main(void) {
int* p1 = (int*)malloc(sizeof(int) * 10);
int* p2 = new int[10];
delete p1; // 等价于free(p1); 可以混搭
free(p2);//等价于delete p2; //可以混搭
system("pause");
return 0;
}
正常运行正常编译。申请连续空间时(就是数组),也能有free和new混搭使用,delete和malloc函数混搭使用。
2、 C++程序员的噩梦-内存泄漏
内存泄漏 ( Memory Leak ) - 是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
#include <stdlib.h>
#include <iostream>
#include <stdio.h>
#include <Windows.h>
using namespace std;
void A_live() {
int* p = new int[1024];
p[0] = 0;
//挥霍、没有释放申请的内存,申请的内存必须要“还”给系统
}
int main(void) {
for(int i=0; i<100000; i++){
A_live();
Sleep(50);
}
return 0;
}
感兴趣的可以运行试一下,运行一段时间,内存申请完以后,电脑没有内存,会卡死。只能强行断电再重新启动,不要轻易尝试。
正确的做法是申请用完以后记得释放,还给系统。
#include <stdlib.h>
#include <iostream>
#include <stdio.h>
#include <Windows.h>
void B_live() {
int* p = new int[1024];//正常的开支
p[0] = 0;
delete[] p;//用完就还给系统
}
int main() {
for (int i = 0; i < 100000; i++) {
B_live();
}
system("pause");
return 0;
}
3、 变量的 4 种存储类型
auto - 函数中所有的非静态局部变量。register - 一般经常被使用的的变量(如某一变量需要计算几千次)可以设置成寄存器变量,register 变量会被存储在寄存器中,计算速度远快于存在内存中的非 register 变量。static - 在变量前加上 static 关键字的变量。之前已经讲过这个关键字了extern - 把全局变量在其他源文件中声明成 extern 变量,可以扩展该全局变量的作用域至声明的那个文件,其本质作用就是对全局变量作用域的扩展
注意:局部变量的作用域受块{ }的限制。但是最新的C++把auto关键字进行了更新,不再表示原来的意思了,在C语言里还是表示局部变量,后面C++11会补充auto关键字!!
注意:register变量不允许取地址
#include <stdlib.h>
#include <iostream>
using namespace std;
static int yahuan_xiaoli = 24;//全局静态变量
void register_demo() {
register int j = 0;//寄存器变量
printf("j: %d\n", j);
//C++ 的 register 关键字已经优化,如果我们打印它的地址,它就变成了普通的 auto 变量
for (register int i = 0; i < 1000; i++) {
//....
}
printf("&j : 0x%p\n", &j);
{
int k = 100;
k += j;
}
printf("yahuan_xiaoli: %d\n", yahuan_xiaoli);
}
int main(void) {
int i = 0; //C 语言的 auto 不会报错,C++ auto 已经升级啦
register_demo();
return 0;
}
虽然register规定不允许取地址,但是通过程序我们发现,其实也是可以取地址的。
#include <stdlib.h>
#include<stdio.h>
#include <iostream>
using namespace std;
//局部静态变量
void static_demo() {
static int girl = 18;
int yahuan = 17;
++girl;
++yahuan;
printf("girl: %d yahuan: %d\n", girl, yahuan);
}
int main() {
static_demo();
static_demo();
static_demo();
}
运行结果:
其实这个static关键字我们讲过很多遍了。它修饰的变量只有在程序结束后才释放它的内存空间。当它在子函数里时,当多次调用该子函数时,它只赋值一次!!
extern就是要用其他文件.c文件的变量,我们要通过extern关键字来声明,这里就不再代码演示了。
4、 变量的作用域和生存周期
5、函数返回值使用指针
#include <iostream>
#include <stdlib.h>
using namespace std;
int* add(int x, int y)
{
int sum = x + y;
return ∑
}
int main()
{
int a = 3, b = 5;
int* sum = NULL;
cout << add(a, b) << endl;
sum = add(a, b);//不能使用外部函数局部变量的地址 bad
cout << *sum << endl;
}
运行结果:
觉不觉得奇怪,因为我们知道子函数的临时变量的内存空间在子函数调用结束后就被释放了,我们虽然把这块内存的地址空间返回了,但是这块空间其实已经给系统不能被用了,但是程序却正常运行了,其实我们不要看运行结果,我们要看逻辑,这种做法显然不对。其实编译是出现了警告的。
下面的做法是可以的:
#include <iostream>
#include <stdlib.h>
using namespace std;
//返回动态内存分配地址
int* add1(int x, int y)
{
int* sum = NULL;
sum = new int;
*sum = x + y;
return sum;
}
int main() {
int a = 3, b = 5;
int* sum = NULL;
sum = add1(a, b);//接收外部函数动态内存分配的地址 ok
cout << *sum << endl;
delete sum;//用完要释放
}
运行结果:
因为堆区要手动 释放,因此这种返回方式是可以的,但是别望了在外部释放,否则就会产生内存泄漏了。
下面这种方式也是可以的:
#include <iostream>
#include <stdlib.h>
using namespace std;
//返回局部静态变量的地址
int* add2(int x, int y)
{
static int sum = 0;
printf("sum: %d\n", sum);
sum = x + y;
return ∑
}
int main() {
int a = 3, b = 5;
int* sum = NULL;
sum = add2(a, b);//接收外部函数局部静态变量的地址
cout << *sum << endl;
*sum = 88888;
cout << *sum << endl;
}
运行结果:
6、常见的错误总结:
1. 申请的内存 多次释放 ,这是容易犯错的一个问题, 多次释放程序会出现异常,会挂起2. 内存泄漏, 只申请,不释放 。3. 释放的内存不是申请时的地址 ,这个问题最大,我们释放的地址只能是从堆区申请的空间,如果释放栈区或其他区的地址,会产生一些列意想不到的问题。4. 释放空指针5. 释放一个内存块,但继续引用其中的内容 ,释放后的空间是不能再次访问的!!6. 越界访问
第一种:多次释放
#include <iostream>
#include <stdlib.h>
using namespace std;
int main()
{
int* p = new int[18];
p[0] = 0;
delete[] p;//只允许一次释放
delete[] p;//1.申请的内存多次释放, 程序出现异常
}
这是一种典型错误,虽然编译能通过,但是是违法的,我们以后在判断程序对不对时不能仅仅只看是否通过编译和运行,因为编译器各有差别,我们要从根源上找原因,因此我们需要扎实的基础功底。
第二种错误:
#include <iostream>
#include <stdlib.h>
using namespace std;
int main() {
char* p1 = NULL;
do{
p1 = new char[10]; //2. 忘记 delete,内存泄漏
}while(1==1);
}
这种也是典型的错误,特别是在子函数里申请内存时,在外部函数最容易忘记释放,从而导致内存泄漏。
第三种错误:释放的内存不是自己手动申请的内存
#include <iostream>
#include <stdlib.h>
using namespace std;
int main() {
//3.释放的内存不是申请时的地址
char a[10] = "1234567";
char* p = a;
for(int i=0; i<sizeof(a)/sizeof(a[0]); i++){
cout<<*(p++)<<endl;
}
delete [] p;
}
运行结果:直接终止程序了。所以释放只能释放自己手动申请的内存。
第四种错误:释放空指针
#include <iostream>
#include <stdlib.h>
using namespace std;
int main() {
//4.释放空指针
char* p1 = NULL;
if (1 == 0) { //比如文件能打开的情况
p1 = new char[2048];
}
delete p1;
}
这种错误能通过编译也能通过运行,但是空指针是指向值为0(NULL)的指针,这个指针不需要释放!!
第五种错误:释放后再次引用该内存的内容
#include <iostream>
#include <stdlib.h>
using namespace std;
int main() {
//5.释放一个内存块,但继续引用其中的内容
char a[] = "1234";
char* p = a;
delete[] p;
p[0]= '\0';//绝对禁止
}
运行结果:直接给你终止程序。
第六种错误:
#include <iostream>
#include <stdlib.h>
using namespace std;
int main() {
//6.越界访问
int* p = new int[3];
memset(p, 0, 3 * sizeof(int));
for (int i = 0; i < 3; i++) {
cout << *(p++) << endl;
}
//误判
for (int i = 0; i < 3; i++) {
cout << *(p++) << endl;
}
cout << "come here!" << endl;
}
运行结果:
运行结果:
因为第一个循环指针已经指向最后一个元素了,此时在往后移动,那么就越界访问了。这种错误也是常见错误,而且还不容易被发现。