指针(日志15)
int p; //这是一个普通的整型变量 int *p; //首先从P 处开始,先与*结合,所以说明P 是一个指针,然后再与int 结合,说明指针所指向的内容的类型为int 型.所以P是一个返回整型数据的指针 int p[3]; //首先从P 处开始,先与[]结合,说明P 是一个数组,然后与int 结合,说明数组里的元素是整型的,所以P 是一个由整型数据组成的数组 int *p[3]; //首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组 int (*p)[3]; //首先从P 处开始,先与*结合,说明P 是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的.所以P 是一个指向由整型数据组成的数组的指针 int **p; //首先从P 开始,先与*结合,说是P 是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据.由于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针. int p(int); //从P 处起,先与()结合,说明P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据 Int (*p)(int); //从P 处开始,先与指针结合,说明P 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以P 是一个指向有一个整型参数且返回类型为整型的函数的指针 int *(*p(int))[3]; //可以先跳过,不看这个类型,过于复杂从P 开始,先与()结合,说明P 是一个函数,然后进入()里面,与int 结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的是一个指针,,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据.所以P 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数.
一、指针
1.1指针的定义
- 指针是一个变量,它存储的是另一个变量在内存中的地址。
- 在C语言中,指针变量定义的一般形式为:数据类型 *指针变量名。
本质上指针就是地址,口语中所说的指针,其实就是指针变量,指针变量是用来存放地址的一个指针。
我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是内存中读取的,处理后的数据也会放回内存中。
电脑上内存是8CB/16GB/32GB等,这些内存空间如何高效的管理?
其实就是把内存划分成一个一个的内存单元,每个内存单元的大小取1字节,每个内存单元都有一个编号。
有了内存单元的编号,CPU就可以快速找到一个内存空间。
内存单元的编号 == 地址 == 指针
要搞清一个指针需要搞清指针的四方面的内容:指针的类型、指针所指向的类型、指针的值或者叫指针所指向的内存区、指针本身所占据的内存区。
1.2指针的类型
从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的类型:
(1)int*ptr;//指针的类型是int*
(2)char*ptr;//指针的类型是char*
(3)int**ptr;//指针的类型是int**
(4)int(*ptr)[3];//指针的类型是int(*)[3]
(5)int*(*ptr)[4];//指针的类型是int*(*)[4]
1.3指针所指向的类型
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:
(1)int*ptr; //指针所指向的类型是int
(2)char*ptr; //指针所指向的的类型是char
(3)int**ptr; //指针所指向的的类型是int*
(4)int(*ptr)[3]; //指针所指向的的类型是int()[3]
(5)int*(*ptr)[4]; //指针所指向的的类型是int*()[4]
在指针的算术运算中,指针所指向的类型有很大的作用。
指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。
1.4指针的值----或者叫指针所指向的内存区或地址
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32 位程序里,所有类型的指针的值都是一个32 位整数,因为32 位程序里内存地址全都是32 位长。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为si zeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX 为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。
注意:以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?
1.5指针本身所占据的内存区
指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32 位平台里,指针本身占据了4 个字节的长度。指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。
二、指针变量和地址
2.1 取地址操作符(&)
创建变量其实就是向内存申请空间
上述代码创建整型变量a,向内存申请了4个字节,用于存放整数10,其中每个字节都有地址,上图中4个字节地址分别是:
0x00F5FCEC
0x00F5FCED
0x00F5FCEE
0x00F5FCEF
那我们如何得到a的地址?
这里就用到取地址操作符(&)
#include <stdio.h> int main() { int a = 10; &a; printf("%p\n", &a); return 0; }
上述代码,运行之后,打印:0x00F5FCEC
&a取出来的是a所占4个字节中地址最小的字节的地址(首地址)。
变量在内存中的存储:
虽然整型变量占4个字节,只要知道第1个字节地址(首地址),就可以顺藤摸瓜访问到其余3个字节的数据。
2.2 指针变量和解引用操作符(*)
2.2.1 指针变量
我们通过取地址操作符(&)拿到的地址是一个数组,比如:0x00F5FCEC,这个数组有时候需要存储起来,方便后期使用,那我们就可以把地址值存放在指针变量中。
总结:变量a和指针变量pa都有各自的地址,只是把变量a的地址存放在指针变量pa里。
指针变量也是一种变量,这种变量就是用来存放地址的,存放在指针变量中的值都会理解为地址。
2.2.2 解引用操作符(*)
#include <iostream> using namespace std; int main() { int a = 10; int* pa = &a;//取出a地址,并存放在指针变量中 //①int说明pa指向的对象是int类型 ②*说明pa是指针变量 *pa=100; printf("%d",*pa); return 0; }
①*的作用是引用指针指向的变量值,引用其实就是引用该变量的地址,“解”就是把该地址对应的东西解开,解出来,就像打开一个包裹一样,那就是该变量的值了,所以称为“解引用”。 也就是说,解引用是返回内存地址中对应的对象。
解引用也可以改变该变量的数值。
②需要注意的是,在变量声明的时候,*不能当做解引用使用,只是表示你声明的变量是一个指针类型。
2.3 指针变量的大小
指针变量的大小取决于地址的大小
①32平台下地址是32bit位,指针变量大小是4字节
②64平台下地址是64bit位,指针变量大小是8字节
注意:指针变量的大小和类型无关,只要指针类型的变量,在相同平台下,大小都是相同的。
三、空指针和野指针
3.1空指针
空指针:指针变量指向内存中编号为0的空间
用途:初始化指针变量
注意:空指针指向的内存是不可以访问的
int main() { //指针变量p指向内存地址编号为0的空间 int *p=NULL; //访问空指针报错 //内存编号0~255为系统占用内存,不允许用户访问 cout<<*p<<endl; return 0; }
3.2野指针
野指针:指针变量指向非法的空间内存
int main() { //指针变量p指向内存地址编号为0x1100的空间 int *p=(int *)0x1100; //访问野指针报错 cout<<*p<<endl; return 0; }
总结:空指针和野指针都不是我们申请的空间,因此不要访问。
五、const修饰指针
const修饰指针有三种情况:
- const修饰指针--常量指针
- const修饰常量--指针常量
- const既修饰指针又修饰常量
5.1const修饰指针--常量指针
const 指针类型 指针名
#include<iostream> using namespace std; int main() { int a = 10; int b = 20; cout << "修改前:" << endl; cout << "a的地址:" << &a<<endl; cout << "a=" << a<<endl; cout << "b的地址:" << &b << endl; cout << "b=" << b << endl; //const修饰的是指针,指针的指向可以变,指针指向的值不可以更改 const int * p1 = &a; cout << "p1:" << p1<<endl; cout << "* p1=" << *p1<<endl; p1 = &b;//正确 // *p1=100;报错 cout << "修改后"<<endl; cout << "a=" << a<<endl; cout << "b=" <<b<<endl; cout << "p1:" << p1 << endl; cout << "*p1=" << *p1; return 0; }
5.2const修饰常量--指针常量
指针类型 const 指针名
#include<iostream> using namespace std; int main() { int a = 10; int b = 20; cout << "修改前:" << endl; cout << "a的地址:" << &a<<endl; cout << "a=" << a<<endl; cout << "b的地址:" << &b << endl; cout << "b=" << b << endl; //const修饰的是变量,指针指向的值可以变,指针的指向不可以更改 int * const p2 = &a; cout << "p1:" << p2<<endl; cout << "* p1=" << *p2<<endl; //p2 = &b;报错 *p2=100;//正确 cout << "修改后"<<endl; cout << "a=" << a<<endl; cout << "b=" <<b<<endl; cout << "p1:" << p2 << endl; cout << "*p1=" << *p2; return 0; }
5.3const既修饰指针又修饰常量
const 指针类型 const 指针名
指针的指向和指针指向的值都不可以更改