本系列文章主要参照C++Primer Plus,以及网上的一些文章。讲述一下在学习C++时碰到的一些重点难点,以及面试时有可能会问的点。本文介绍的是指针的内容。
指针
计算机存储数据必须有的3个属性为
- 信息存储的地方
- 信息的值
- 信息的类型
其中指针是用于跟踪数据在内存中的位置,知道了指针的值,就能够在内存找到其位置,并对其操作。指针的简单应用可以参考其他文章,在这里不赘述。
1. new关键字
new是在C++中才引入的用来分配内存的关键字,在C中是用malloc来分配内存。
1.1 new与delete
和C中的malloc、free对应类似,在C++中用new分配内存后,如果该内存不再使用,则应该用delete释放,否则该内存在整个程序运行周期将一直被占用,也就是内存泄漏,如果程序很大时,可能会导致崩溃。
new 与delete的应用如下:
int *pt = new int;
//some code here
delete pt;
//some different for dynamic array
int *pt = new int[10];
//some code here
delete []pt;
当用new创建一个指针时,用delete直接将其删除即可,如果创建的是一个动态数组,则在delete后需要加一个中括号[],才能将整段数组空间释放,否则只是释放该指针指向的第一个元素的空间。
1.2 new 与 malloc的异同
在面试时,有时会问到new与malloc的联系,下边总结一下我能理解的异同。
- 1.返回类型不一样
在使用new分配内存时,返回的内存就是对象类型的指针。同时如果对象是类,则还会调用类的构造函数,初始化成员变量,再返回对象类型的指针。如下
int *a = new int;//返回的是类型为int的指针
SomeClass *b = new SomeClass();// 返回的是类SomeClass的指针,并调用默认构造函数
而用malloc时,返回的是一个void(*)的指针,如果要使用的话需要用强制类型转换。并且由于malloc是C中的库函数,因此没有调用类的构造函数的权限,因此整个过程只是声明了一块空间。
int* p;
p = (int *) malloc (sizeof(int));// malloc返回的指针需要用强制类型转换为int*类型
- 2.两者在分配内存失败时的操作不一样
在用new分配内存时,如果分配失败,会抛出bad_alloc异常终止程序。
而用malloc分配内存时,如果分配失败,则会返回一个空指针,程序会继续运行。
所以在用malloc时,通常要在其后边添加判断其是否空指针的判断代码。
- 3.分配内存时是否需要指定大小
在用malloc分配内存时,需要显示指定其分配空间的大小。
而用new的话,会根据类型进行自动计算需要分配空间的大小。
int *a = (int*)malloc(sizeof(int));//分配4个字节大小的空间
int *b = new int;//分配4个字节大小的空间
int *c = (int*)malloc(sizeof(int)*100)//分配可储存100个整形变量的空间
int *d = new int[100]; //分配100个整形变量空间并返回第一个空间的指针
- 4.是否有调用构造函数和析构函数
在使用new和delete来声明类时,除了分配以及释放空间,还会根据声明的类型来调用类的构造函数和析构函数,而malloc则没有这个功能。
原因是malloc是C中的库函数,没有能够调用类的构造和析构函数的权限,而new是C++中的运算符,因此可以调用。
int main(){
A *a = new A();//创建一个A类大小的储存空间,并调用构造函数,此处可调用默认构造函数
delete a;//释放a的空间,并调用其析构函数
return 0;
}
- 5. 两者的内存区域不一样
对于malloc,其分配的内存空间位于堆中。堆是操作系统的术语,是操作系统维护的一块的内存,用于程序的内存分配。
对于new,其分配的内存空间位于自由存储区,自由存储区是基于new的一个抽象术语,凡事用new分配的内存就称为是自由存储区,其区域可以不位于堆中。
2 指针算术
指针算术其实很简单,以前使用比较少所以不太熟悉。在一些笔试题中使用指针算术的话十分方便。
C++中允许指针和整数相加,加1的结果是原来地址值加上指向对象的总字节数。如下:
int a[10] = {9,8,7,6,5,4,3,2,1,0};
int *pt = a;
cout<<*pt<<endl;//打印 9,也就是a[0]
pt++;
cout<<*pt<<endl;//打印 8
pt+=2;
cout<<*pt<<endl;//打印 6
在C++中,数组名是解释为第一个元素的地址,因此将a赋值给pt后,pt指向的就是a的第一个元素,pt++后指向a的第二个元素,以此类推。
3 指针与字符串
在C++中,引入了string作为表示字符串的方式,但是这里不讨论string的用法,而是说一下用字符指针来表示C-风格字符串的方式。
char flower[10] = "rose";
cout<<flower;
以上代码创建一个长度为10的字符数组并将前4个字符初始化为"rose"。但是在输出的时候也只是输出rose,并不是只输出第一个字符r,也没有输出后边6个字符。
这是因为在初始化字符时,默认会首先将每个字符设置为空字符"\0",然后再根据情况对其初始化。在输出时程序会从第一个字符开始逐个打印,直到碰到空字符就认为已经到达字符串的末尾,之后的内容就不再输出。
char animal[10] = "bear";
char *ps;
ps = animal;
cout<<animal<<" "<<(int*)animal<<endl;
cout<<ps<<" "<<(int*)ps<<endl;
ps = new char[strlen(animal)+1]
strcpy(ps,animal);
cout<<animal<<" "<<(int*)animal<<endl;
cout<<ps<<" "<<(int*)ps<<endl;
以上代码可以两部分,第一部分声明了animal的字符串并将其初始化为bear,之后通过等于号将其赋给字符串指针ps。然后打印两个变量的值和地址,正常输出的话animal和ps两个变量的值和地址是一模一样的。意味着这两个变量是指向同一块内存单元,这时如果对其中一个进行修改的话也会改变另一个,这应该不是我们想要的。
这时因为在进行ps=animal的赋值运算时,并不是将animal变量的内容赋值给ps,而是把地址赋值,因此完成后两者的地址和变量变得一模一样。
如果需要将animal的值赋予给ps,但是并不赋予地址,这时可以用代码下半部分的方法,利用strcpy函数来实现这个功能。首先在ps中申请好足够的空间,代码中是用new申请足够空间给ps指针。然后再应用strcpy函数将animal中的字符串内容拷贝到ps里。打印两个变量发现,此时它们的地址已经不一样。