一、什么是指针?
*是 取指针目标运算符
指针变量名与内存位置之间的关联由编译器实现,硬件可通过地址访问内存位置
指针是有类型的,指针的类型决定了指针的±整数的步长,指针解引用操作的权限。
- 指针就是地址,地址就是指针;指针变量是一个变量,它保存了基本类型变量的地址。
- 如果指针变量p保存了变量a的地址,那么称作p指向了a,*p 就是变量a。
- 如果p是一个指针变量,*p 表示了以 p 的内容为地址的变量,就是p指向的变量。
地址就是可以唯一标识某一点的一个编号,即一个数字,通过地址运算符"&"得到变量的地址。
变量相当于是一个容器,用来存放数据的,变量是存放在内存中的。在C++ 中定义变量的形式是: 数据类型 + 变量名,这里的变量名实际上是一个符号地址,在程序编译时,操作系统将为每个变量在内存中分配内存,所以每个变量都有一个在内存中的地址,即物理地址。并将变量的符号地址(变量名)和物理地址关联起来,所以,我们在程序中对变量名的操作,编译时编译器都会将变量名转换为变量在内存中的物理地址,从而实现了对内存中指定地址区域的数据的操作,这就是变量的实现原理。变量在内存中的地址又称作指针,我们说“变量的地址” 就等价于 “变量的指针”,但是指针和指针变量是不一样的。
指针是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。变量在内存中的地址又称作指针,我们说“变量的地址” 就等价于 “变量的指针”,但是指针和指针变量是不一样的。
指针变量用来存放普通变量的地址,即指针变量是用来存放普通变量的指针。指针变量也是一个变量,在内存中也是占内存的,只不过它不存放基本类型数据,而是存放其他基本类型变量的地址。那指针变量也有物理地址,比该指针类型高一级的指针变量来存放指针变量的地址,如二级指针变量存放一级指针变量的地址。
变量的指针就是变量的地址。 存放变量地址的变量是指针变量。 允许用一个变量来存放指针,这种变量称为指针变量。 因此,一个指针变量的值就是某个变量的地址或称为某变量的指针
- 比如: int a;//变量
- int *p;//指针变量
- p=&a;//p就是变量a的指针,你可以叫它变量指针
int a ; //定义int类型变量
int p = &a; //变量 p 是一个 int 类型的一级指针变量,&是取地址符,p保存了a 的地址
cout << *p <<endl; //输出 p 指向变量的值,即输出a的值
cout << p << endl; //输出 p 的值,即输出变量a在内存中的地址
int **q; //定义二级指针变量
q = &p; // 二级指针变量q保存了一级指针变量p的地址
cout << q <<endl; //输出指针变量p在内存中的地址
cout << *q << endl; //输出q指向变量的值,即变量p的值,即a的地址
cout << **q << endl; //可以这样理解 cout<<*(*q), 等价于 cout <<*p, 即输出a的值
(存放在指针中的值都被当成地址处理)
指针是(地址)≠指针变量(是变量)
main 函数内的变量不是全局变量,而是局部变量,只不过它的生命周期和全局变量一样长而已。
全局变量一定是定义在函数外部的
声明一个指针变量并不会自动分配任何内存,在对指针执行间接访问前,指针必须进行初始化,或者使它指向现有的内存,或者给它分配动态内存
将数值存储到指定的内存地址 —— 往内存0x12ff7c 地址上存入一个整数0x100
int * p=(int *)0x12ff7c
*p=0x100;//即*(int *)0x12ff7c = 0x100;
指针的大小是固定的4/8个字节(32位平台/64位平台)
void不可以定义变量。因为定义变量前提要开辟空间,而void是空类型。
void可以,但void 定义的变量不可被解引用。
通过一个指针访问它所指向的地址的过程称为间接访问或解引用指针。
二、指针的运算
-
对指针进行加 1 操作
得到的是下一个元素的地址,而不是原有地址值直接加 1。所以,一个类型为 T 的指针的移动,以 sizeof(T) 为移动单位。
一级指针 加上指向其类型的大小 多级指针 直接+4 -
指针± 整数
只能用于数组中指向某个元素的指针
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一 个元素之前的那个内存位置的指针进行比较。
若结果所指向的位置在数组第1个元素之前或在最后1个之后,其效果是未定义的。
如果 — 操作后结果产生的指针所指向的位置在数组第一个元素之前,那么它是非法的。
让指针指向数组最后1个元素后面的那个位置是合法的,但对这个指针执行间接访问可能会失败。
对于 + 结果指针指向数组最后一个元素后面的哪个内存地址仍然合法(但不能对这个指针执行间接访问操作,不过再往后不合法了。
-
指针-指针
只有当两个指针都指向同一个数组中的元素时,才允许从一个指针减去另一个指针。
结果是两个指针在内存中的距离(以数组元素的长度为单位)。
若p1指向
array[i]
p2指向array[j]
则 p2-p1 就是 j-i 的值
p1-p2 是合法的是 i-j 的值
若两指针指向的不是同一个数组中的元素,我们不知道两个数组在内存中的相对位置,两个指针之间的距离就没有意义。
-
指针的关系运算
< <= > >=
前提是指向同一数组中的元素
三、指针常量
指针常量的本质是一个常量,并且使用指针来修饰它,那么说明这个常量的值应该是一个指针
其格式为: int const * p
或者 const int* p
声明:
int a;
int *const p = &a;
*(指针)和 const(常量) 谁在前先读谁 ; *象征着地址,const象征着内容;谁在前面谁就不允许改变。
间接访问操作只能作用于指针类型表达式,若要把25存储于位置100必须要强制类型转换。
*(int *)100 = 25;
四、二级指针
char **p;
定义了一个二级指针变量 p。p 是一个指针变量,在 32 位系统下占 4 个 byte。
与一级指针不同的是,一级指针保存的是数据的地址,二级指针保存的是一级指针的地址。
*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa .
**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .
int a = 12;
int *b = &a;
int **c = &b;
双重间接访问
表达式 | 相当的表达式 |
---|---|
a | 12 |
b | &a |
*b | a,12 |
c | &b |
*c | b,&a |
**c | *b,a,12 |
五、NULL指针
表示不指向任何东西,要使一个指针变量为NULL,可以给它赋一个0值;
为了测试一个指针变量是否为NULL,可以将它与0进行比较;
所以,在对指针进行解引用之前,确保它并非NULL指针
它被宏定义为 0: #define NULL 0
*&a = 25;
把值25赋给a。&产生变量a的地址。它是一个指针常量,接着*访问其操作数所表示的地址。
六、函数指针
函数指针是指向函数的指针变量。C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向地址。有了指向函数的指针变量后,可用该指针变量调用函数
函数指针有两个用途:调用函数和做函数的参数。
int func (int x); /*声明一个函数 */
int (* f) (int x); /*声明一个函数指针 */
f=func; /*将func函数的首地址赋给指针f */
或者使用下面的方法将函数地址赋给函数指针:
f = &func;
七、数组指针和指针数组
指针数组 (储存指针的数组) 是数组,数组的元素都是指针,数组占多少字节由数组本身决定。
数组指针 (指向数组的指针) 是指针 它指向一个数组,在32位系统下占4字节。
int (* p)[10]
p先和 * 结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针 。储存储在哪里,我们并不知道。
[]的优先级要高于 * 号的,所以必须加上()来保证p先和 * 结合。
int *p[10];
p 先与“[]”结合,构成一个数组的定义,数组名为 p1,int* 修饰的是数组的内容,即数组的每个元素。这是一个数组,其包含 10 个 指向 int 类型数据的指针,即指针数组。
八、a与 &a
&a[0] 和&a[] (a与 &a)
a[0]是一个元素 a[]是整个数组 虽然他们的值一样,但意义不一样
前者是数组首元素的首地址,后者是数组的首地址。
同理,a与 &a的值是一样的,但意义不同,前者是数组首元素的首地址,后者是数组的首地址。
九、数组名a作左右值区别
简单而言,出现在赋值符“=”右边的就是右值,出现在赋值符“=”左边的就是左值。 比如,x=y。
-
a 作为右值时其意义与&a[0]是一样,代表的是数组首元素的首地址;
-
**a 不能作为左值!**编译器会认为数组名作为左值代表的意思是a的首元素的首地址但是这个地址开始的一块内存是一个总体我们只能访问数组的某个元素而无法把数组当一个总体进行访问。
所以我们可以把 a[i]当左值,而无法把 a 当左值。