指针知识点总结
1. int *p; 指针与引用
一个“int *”类型的模子在内存上占用4个字节的空间,然后把这个空间命名为p,同时限定这4个字节的空间里面只能存储某个内存地址,即使你存入别的类型数据,也当作地址处理。
举例:在32位系统下指针变量的大小为4个字节。与所指向的变量类型无关。
(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;
引用:跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。如:
int a=1;int *p=&a;
int a=1;int &b=a;
//b的值也是1。。
(2)可以有const指针,但是没有const引用;
关于const 指针现在简单说明一下。
int a = 5;
int b = 8;
const int* pn = &a; // 正确.指向的内容是常量,不能更改,但可以改变指针的内容
pn = &b; // 正确.地址变了,指向就变了,就指向b了。
*pn = 3 ; // 错误.
cout<<*pn<<endl; //8
cout<<a<<endl; //5
int a = 5;
int b = 8;
int* const pn = &a; // 正确.指针的内容是常量,但指向的变量内容可以更改
//pn = &b; // 错误.
*pn = 3 ; // 正确
cout<<*pn<<endl; //3
cout<<a<<endl; //3
return 0;
关于const 指针总结下:
把*读作"pointer to",从右至左念:
b是一个常量
const int b; /* b is a int const */
int const b; /* b is a const int */
p是一个普通指针,指向一个常量
const int *p; /* p is a pointer to int const */
int const *p; /* p is a pointer to const int */
p是一个常量指针,指向一个普通变量
int *const p; /* p is a const pointer to int */
p是一个常量指针,指向一个常量
const int *const p; /* p is a const pointer to int const */
int const *const p; /* p is a const pointer to const int */
(3)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
(4)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;
(5)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。
(6)”sizeof引用”得到的是所指向的变量(对象)的大小,而”sizeof指针”得到的是指针本身的大小;
(7)指针和引用的自增(++)运算意义不一样;
指针与引用作为函数参数
引用作为函数参数进行传递时,实质上传递的是实参本身,即传递进来的不是实参的一个拷贝,因此对形参的修改其实是对实参的修改,所以在用引用进行参数传递时,不仅节约时间,而且可以节约空间。
指针作为函数参数有两种情况:
1
#include<iostream>
using namespace std;
void test(int *p)
{
int a=1;
p=&a;
cout<<p<<" "<<*p<<endl;
}
int main(void)
{
int *p=NULL;
test(p);
if(p==NULL)
cout<<"指针p为NULL"<<endl;
system("pause");
return 0;
}
运行结果为:
0x22ff44 1
指针p为NULL
main中的p的地址与形参p的地址一样,但是是拷贝,不是同一个。所以test()中改变p与main函数无关。
第二种情况:
void swap(int *aa,int *bb)
{
cout<<"swap函数交换前,aa,bb的内容 "<<aa<<' '<<bb<<endl;
cout<<"swap函数交换前,aa,bb的地址 "<<&aa<<' '<<&bb<<endl;
int temp = *aa;
*aa = *bb;
*bb = temp;
cout<<"swap函数交换后,aa,bb的内容 "<<aa<<' '<<bb<<endl;
cout<<"swap函数交换后,aa,bb的地址 "<<&aa<<' '<<&bb<<endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
int a=1,b=2;
cout<<"主函数a,b地址 "<<&a<<' '<<&b<<endl;
swap(&a,&b);
cout<<"交换后主函数a,b值 "<<a<<' '<<b<<endl;
cout<<"交换后主函数a,b地址 "<<&a<<' '<<&b<<endl;
system("pause");
return 0;
运行结果为
主函数a,b地址 0012FF60 0012FF54
swap函数交换前,aa,bb的内容 0012FF60 0012FF54
swap函数交换前,aa,bb的地址 0012FE7C 0012FE80
swap函数交换后,aa,bb的内容 0012FF60 0012FF54
swap函数交换后,aa,bb的地址 0012FE7C 0012FE80
交换后主函数a,b值 2 1
交换后主函数a,b地址 0012FF60 0012FF54
注:swap函数交换的不是指针的地址也不是指针的值而是指针指向的变量的值。
2. int *p=NULL和*p=NULL的区别
int *p=NULL; 这句代码意思:定义一个指针变量p,内存里保存的是int类型的数据。同时把p的值设为0x00000000.即为初始化。
int i=10;
int *p=&I;
*p=NULL;
结果是*p的值由10变为0,地址p不变。
程序中 #define NULL 0 //宏定义为0.
指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。否则会变成野指针。同样指针被free和delete后也要赋值NULL.
3. 将数值存储到指定的内存地址
int *p = (int *)0x12ff7c;
*p = 0x100;
0x12ff7c被强制类型转换成指向int 类型变量的指针。
还有以下表达形式:
*(int *)0x12ff7c = 0x100;
而(int)0x12ff7c+1
是将地址强制变换成成整型,再加1个字节。
//在内存0x12ff7c地址上存入一个整型数0x100。
4. 数组
int a[10]={0};
cout<<a<<endl;
cout<<&a<<endl;
//两者输出的都是数组a的地址
int a[2]={0,3};
int *p=a;
cout<<p<<' '<<p[0]<<' '<<*p<<endl;
p++;
p是地址,p[0]是数组第一个元素,*p也是第一个元素
p加的不一定是1,此处加的是4,因为数组b是整型。换句话说加的是偏移量,偏移量的单位是元素个数而不是byte数
a与&a a是数组首元素的首地址,也即是a[0]的地址。&a是数组的首地址。a+1是数组下一个元素的首地址即a[1]的地址, &a+1是下一个数组的首地址。所以对指针进行加1操作,得到的是下一个元素的地址。
5. 指针数组和数组指针
指针数组:首先他是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。
数组指针:他首先是个指针。指向一个数组。在32位系统下永远占用4个字节。
A) int *p1[10];
B) int (*p2)[10];
“[]”的优先级比“”要高。A,数组名为p1,int 修饰的是数组的内容。即指针数组。
P2是个指针。B,数组没有名字,是个匿名数组
内存是线性的。
举例:
int array[2][10] ={{1,2,3,4,5,6,7,8,9,10},{11,12,13,14,15,16,17,18,19,20}};
int (*p)[10] = array;//数组指针
cout<<p[0]<<endl;
cout<<p[1]<<endl;
cout<<&array[0][0]<<endl;
cout<<&array[1][0]<<endl;
cout<<*(p[0]+1)<<endl;
cout<<*(*(p+1)+1)<<endl;
cout<<*(p[1])<<endl;
cout<<**(p)<<endl;
cout<<**(p+1)<<endl;
int *q[2] ;//= array;
for(int i=0;i<2;i++)
q[i]=array[i];
cout<<q[0]<<endl;
cout<<q[1]<<endl;
cout<<*q[0]<<endl;
cout<<*(q[0]+1)<<endl;
cout<<*q[1]<<endl;
cout<<q[0][1]<<endl;
cout<<*(*(q+1)+1)<<endl;
p是一个指针,指向一个整型的一维数组,这个一维数组的长度是10。
p的增量以它所指向的一维数组长度为单位。
所以p[0]是array[0][0]的地址。p[1]是array[1][0]的地址。
*q[2]
是指针数组,实质是一个数组,里面的两个元素都是指针, []的优先级比*
的优先级高,q先与[]结合,形成数组q[2],有两个元素的数组,再与*
结合,表示此数组是指针类型的,每个数组元素相当于一个指针变量。对于每个指针变量都要赋值。q[0]表示array[0][0]的地址。
*(q[0]+1)
等价于q[0][1]等价于*(*(q+0)+1)
6. 指针与字符串
char P[] ="I love china";
char *pp = P;
cout<<P<<endl; //输出的是字符串"I love china",和整型数组不同
cout<<pp<<endl; //输出的是字符串
char (*ppp)[13] = &P ; //数组大小必须为13,和P大小一致
cout<<*ppp<<endl; //输出的是字符串
7. 二级指针
char **P;
一级指针保存的是数据的地址,二级指针保存的是一级指针的地址。
char *s1="abc";
char **s2=&s1;
cout<<s1<<endl;
cout<<*s1<<endl;
cout<<&s1<<endl;
cout<<*s2<<endl;
cout<<**s2<<endl;
cout<<s2<<endl;
/*
abc
a
0012FF60
abc
a
0012FF60
*/
8. 数组参数和指针参数
在C语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。
9. 函数指针
A) char *(*fun1)(char *p1,char *p2);
B) char **fun2(char *p1,char*p2);
C) char *fun3(char *p1,char *p2);
C): fun3是函数名,p1,p2是参数,其类型为char*型,函数的返回值为char*类型
B) 函数返回类型为char**,是个二级指针
A) fun1 是个函数指针变量,指向一个函数,函数有两个指针类型的参数,函数的返回值也是一个指针。。形式类似数组指针。
10. 内存管理
内存分为三个部分:静态区,栈,堆
首先堆栈就是栈。下面分别介绍。
静态区:保存自动全局变量和static变量(包括static全局和局部变量),静态区的内容在整个程序的生命周期内都存在,由编译器在编译的时候分配。
栈:保存局部变量。栈的内容只在函数的范围上存在,当函数运行结束,这些内容会自动被销毁。其特点是效率高,但空间大小有限。
堆:由malloc系列函数或new操作符分配的内存。其生命周期由free或delete决定
另外还有一个专门放常量的地方
一. 在c中分为这几个存储区
1.栈 - 由编译器自动分配释放,存放一般变量
2.堆 - 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收
用malloc, calloc, realloc等分配内存的函数分配
3.全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。- 程序结束释放
4.另外还有一个专门放常量的地方。- 程序结束释放
二.在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区
1.栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。
2.堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
3.自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。
4.全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。
5.常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改)
在C语言中,用const定义的常量其实是值不能修改的变量,因此会给它分配存储空间;
但是在C++中,const定义的常量要具体情况具体对待:对于基本数据类型的常量,编译器会把它放到符号表中而不分配存储空间,而ADT/UDT的const对象则需要分配存储空间(大对象)。还有一些情况下也需要分配存储空间,例如强制声明为extern的符号常量或取符号常量的地址等操作。
内存管理需要注意的问题:
10.1 指针没有指向一块合法的内存
1) 结构体成员指针未初始化,这是比较隐蔽的问题。
比如结构体内部的成员变量未初始化
2)没有为指针分配足够的内存
char p = (char)malloc(sizeof(char)); //分配大小
//(void*)malloc (int size); size的单位是byte,一般需要强制转化
char *P = NULL; //指针初始化
memset(a,0,sizeof(a));
memset 函数有三个参数,第一个是要被设置的内存起始地址;第二个参数是要被设置的值;第三个参数是要被设置的内存大小,单位为byte。
3)内存泄漏
与malloc 对应的就是free 函数了。
我们可以说malloc 函数分配的内存块是属于p 的,因为我们对这块内存的访问都需要通过p 来进行。free 函数就是把这块内存和p 之间的所有关
系斩断。从此p 和那块内存之间再无瓜葛。至于指针变量p 本身保存的地址并没有改变,但是它对这个地址处的那块内存却已经没有所有权了。那块被释放的内存里面保存的值也没有改变,只是再也没有办法使用了。
一般由于malloc或new 操作符分配的内存没有及时free或delete,内存释放后一定要把指针变量变为NULL,即p = NULL;