目录
一,指针的概述
1 指针的意义
1.1 指针的引入
1.2 指针的好处
2 指针的理解
3 一级指针的定义和使用
二,指针的运算
1 指针的算术运算
2 指针关系运算和逻辑运算
三,数据大小端存储
四,数组和指针
1 一维数组和指针
1.1 一维数组和指针的理解
1.2 一维数组和指针的运用
2 二维数组和指针
2.1 行指针
2.2 列指针
3 指针数组
3.1 指针数组定义语法
3.2 指针数组元素访问
3.3 指针数组元素初始值
3.4 指针数组的运用
五,多级指针
多级指针的理解
六, 特殊指针
1 NULL指针
2 野指针
3 void类型指针
七,const指针
1 const数据类型变量
2 const修饰指针
2.1 const修饰指针目标
2.2 const修饰指针指向
2.3 const修饰指针目标和指向
C语言中的数据,分为常量和变量;
常量存储数据常量区;变量会根据变量的存储属性,存储位置不同:全局变量和static修饰的局部变量存储在静态存储区、而auto修饰的局部变量存储在栈区。
其实质:C在Linu语言中的所有数据都需要存储。在Linux系统中,程序的运行过程称为进程。
进程是资源管理的最小单位:在进程创建的时候,系统会为进程构造虚拟内存用于数据的存储。在虚拟内存中数据以字节为单位存储,内存空间的每一个字节空间都会有编号。
在32位操作系统中:虚拟内存空间的大小为4G,虚拟内存空间编号(0x0000 0000 ~ 0xffff ffff);
寄存器所存储数据的值域范围:0x00000000 ~ 0xffffffff;
在64位操作系统中
虚拟内存编号,其实质就是内存空间的地址,也就是指针。
提高程序的运行效率:其实质是通过内存编号实现内存空间数据的访问;
实现连续内存空间的访问;
对于指针访问的时候,可以通过指针数据类型实现不同空间大小的访问。
在函数中使用指针,可以实现多个数据的输入输出。
通过指针可以实现对硬件物理地址空间或者寄存器的访问。
指针的概念:
所谓的指针,其实质是存储空间的地址,而空间的地址需要关注两个部分:
指针的值:表示空间起始字节编号;
指针的指向(数据类型):
数据类型所占存储空间的大小也就是指针所指向存储空间的大小(所取数据的字节数);
存储编码的解码形式。
存储类型 数据类型 *指针变量名;
存储类型:修饰的是指针变量存储空间的属性;
数据类型:修饰的是指针指向空间的数据类型,而不是指针变量本身的数据类型。指针本身没有数据类型,在32位操作系统中,所有的指针都是使用4字节存储空间存储。
*符号:是一个说明符,表示所定义的变量位指针变量(一级指针变量);
指针变量名:是一个新的变量名称,在定义的时候需要满足标识符的命名规则。
2.指针变量的初始值
在定义指针变量的时候:
1. 可以给指针变量设置初始值,指针变量值即为设置初始值;
2.也可以不设置初始值,默认值与指针变量的存储位置有关
静态存储区默认初始值为零值(NULL);
栈区默认初始值为随机值;
3.指针的使用
实质是对指针所指向空间的解引用访问。
#include
int main()
{
int a = 123;
#if 0
int *p; /* 定义指针变量 */
p = &a;
/* 设置指针变量的初始值 */
#else
int *p = &a; /* 在定义指针变量的时候,同时对指针变量设置初始值 */
#endif
printf("a = %d, *p = %d, *(&a) = %d\n", a, *p, *(&a));
*p = 321; /* 对指针变量解引用访问,修改指针所指向空间的数据 */
printf("a = %d, *p = %d, *(&a) = %d\n", a, *p, *(&a));
*(&a) = 333; /* 对指针常量解引用访问,修改指针所指向空间的数据 */
printf("a = %d, *p = %d, *(&a) = %d\n", a, *p, *(&a));
}
对于指针的运算符,所针对的是具有相同数据类型的连续存储空间的指针运算才有意义。
指针 + 常量n:表示指针偏移,指针向内存空间的高地址方向偏移常量n个元素空间。
指针 - 常量n:表示指针偏移,指针向内存空间的低地址方向偏移常量n个元素空间。
指针 - 指针:表示两个指针之间相差元素的个数;
++:
前++(++p):向将指针p自加1(先高地址方向偏移一个元素),在取指针p的值;
后++(p++):向取指针p的值,在将指针p自加1;
- -:
前--(--p):向将指针p自减1(先低地址方向偏移一个元素),在取指针p的值;
后--(p--):向取指针p的值,在将指针p自减1;
注意:实质是运用于指针之间关系的判断。
在虚拟内存中,数据的最小存储单元是以字节为单位存储。
单字节数据(char数据)直接存储;
多字节数据,在虚拟内存中以字节为单位存储,可以有不同存储方式实现:
对于多字节数据,如果以字节为单位,数据有高低位之分;而数据的存储空间有高低地址之分;可以根据数据的高低位在内存中高低地址中的存储不同分为小端存储和大端存储两种方式:
所谓的小端存储:数据的高位在内存高地址端存储,数据的低位在内存低地址端存储;
所谓的大端存储:数据的高位在内存低地址端存储,数据的低位在内存高地址端存储;
#include
int main()
{
int a = 0x12345678;
int i;
char *p = (char *)&a;
for (i = 0; i < 4; i++) {
printf("%x ", *(p++));
}
}
#include
int main()
{
int i;
int a[5] = {1,2,3,4,5}; /* 定义一个一维数组 */
int *p = a;
/* 定义一个指针存储数组首元素地址 */
printf("a[i]:");
for (i = 0; i < 5; i++)
printf("%d ", a[i]);
printf("\n");
printf("p[i]:");
for (i = 0; i < 5; i++)
printf("%d ", p[i]);
printf("\n");
printf("*(a+i):");
for (i = 0; i < 5; i++)
printf("%d ", *(a+i));
printf("\n");
printf("*(p+i):");
for (i = 0; i < 5; i++)
printf("%d ", *(p+i));
printf("\n");
#if 0
printf("*a++:");
for (i = 0; i < 5; i++)
printf("%d ", *a++); /* 语法错误:a为指针常量,不能进行自加减运算符 */
printf("\n");
#else
printf("*p++:");
for (i = 0; i < 5; i++)
printf("%d ", *p++);
printf("\n");
#endif
}
int main()
{
int arr[5] = {6,7,8,9,10};
int *p = arr; /* 定义指针变量p存储数组首元素地址 */
*(p++) += 123; /* p++:先取值,在自加,跟括号无关,语句等价于:*p += 123; p++ */
printf("%d,%d\n", *p, *++p); /* printf语句中表达式的执行时从右向左顺序执行 */
}
程序分析:
int main()
{
int arr[5] = {1,2,3,4,5};
printf("%d,", *((int *)(&arr+1)-1));
printf("%x", *(int *)((unsigned long)arr+1));
}
实例分析:
在二维数组中,对于数组元素的访问,可以使用行指针和列指针实现。
在二维数组中,数组名是一个指针常量,所指向的空间为二维数组中的首行元素,所以数组名所表示的指针常量称为行指针。也称为数组指针,表示指针所指向空间为数组空间。
#include
int main()
{
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
int (*p)[4] = a; /* 定义指针变量存储指针常量的值,此时的指针变量也称为行指针、或者数组指针 */
printf("%p - %p\n", &a, &a+1);
printf("%p - %p\n", a, a+1);
printf("%p - %p\n", a[0], a[1]);
printf("a[i][i]: %d\n", a[2][3]);
printf("*(*(a+i)+j): %d\n",*(*(a+2)+2));
printf("*(a[i]+j): %d\n", *(a[2]+1));
printf("(*(a+i))[j]: %d\n", (*(a+2))[0]);
printf("p[i][i]: %d\n", p[2][3]);
printf("*(*(p+i)+j): %d\n",*(*(p+2)+2));
printf("*(p[i]+j): %d\n", *(p[2]+1));
printf("(*(p+i))[j]: %d\n", (*(p+2))[0]);
}
在二维数组中,所有的数据元素存储空间为线性存储空间,所的数据元素以行的顺序存储,以列的顺序存储(下一行起始元素存储上一行元素的下一个位置)。将整个二维数组元素看作一维数组访问。
此时起始元素地址称为列指针。
#include
int main()
{
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
int *q = &a[0][0]; /* 定义的指针,存储二维数组首元素地址 */
int i;
int j;
for (i = 0; i < sizeof(a)/sizeof(a[0]); i++) {
for (j = 0; j < sizeof(a[0])/sizeof(a[0][0]); j++) {
printf("%d ", *(q+i*4+j));
}
printf("\n");
}
}
所谓的指针数组,实质是数组,数组中每一个元素是指针。也就是说所谓的指针数组指的是具有相同数据类型的连续内存空间中存储的多个指针所构成的集合,称为指针数组。
存储类型 数据类型 *指针数组名[常量表达式];
存储类型:修饰整个数组集合空间存储位置属性,同时也表示的数组集合中元素存储空间属性;
数据类型:表示数组元素指针所指向空间的数据类型;
*特殊符号,表示数组中所存储数据元素为指针;
指针数组名:实质就是数组集合变量名;
常量表达式:表示集合中数据元素的个数;
不能整体访问,只能逐一元素访问,所访问的每一个元素都为指针。
数组名[下标];下标范围:[0,数组元素个数)
只做定义,未设置初始值
元素的初始值有存储位置有关,
静态存储区:初始值为零值(NULL);
栈区:初始值为随机值;
定义同时设置初始值
有设置值元素的初始值为设置的初始值,未设置元素的初始值为随机值。
在指针数组中的数据元素,初始值可以设置为单个数据存储空间起始地址,也可以设置为数组首元素存储空间起始地址。为了实现数据的有效访问,在设置过程中尽可能做到数据统一化:
将所有指针数组元素设置为单个数据的地址;
将所有指针数组元素设置为数组首元素地址,并且所指向数组中元素个数相同;
不统一的数据,可以采用数据封装为统一后实现。
#include
int main()
{
int i;
int j;
int a = 3;
int b = 5;
int c = 6;
int d = 7;
int *arr[4] = {&a, &b, &c, &d};
for (i = 0; i < 4; i++) {
printf("arr[%d] : %d\n", i, *(arr[i]));
}
int Arr1[5] = {1,2,3,4,5};
int Arr2[5] = {11,12,13,14,15};
int Arr3[5] = {11,22,33,44,55};
int *arr1[3] = {Arr1, Arr2, Arr3};
for (i = 0; i < 3; i++) {
printf("Arr%d:",i);
for (j = 0; j < 5; j++) {
printf("%d ", arr1[i][j]);
}
printf("\n");
}
}
所谓的多级指针,其实质指的是指针的指针,称为多级指针。
#include
int main()
{
int a = 123;
int *p = &a;
int **q = &p;
printf("a = %d\n", a);
printf("a = %d, *p = %d\n", a, *p);
printf("a = %d, *p = %d, **q = %d\n", a, *p, **q);
*p = 3; /* 通过一级指针访问修改内存数据 */
printf("a = %d\n", a);
printf("a = %d, *p = %d\n", a, *p);
printf("a = %d, *p = %d, **q = %d\n", a, *p, **q);
**q = 321; /* 通过多级(二级)指针访问修改内存 */
printf("a = %d\n", a);
printf("a = %d, *p = %d\n", a, *p);
printf("a = %d, *p = %d, **q = %d\n", a, *p, **q);
}
NULL实质是标识符常量,标识指针的零值:((void *)0),也称为空指针。
在程序预处理阶段,做替换。
注意:在给定的指针变量和零值判断的方式:
1) 指针变量p直接和指针零值进行比较:
if (p == NULL) {
/* 条件成立,说明指针变量p的值为指针零值 */
} else {
/* 条件不成立,说明指针变量p的值不为指针零值 */
}
/* 同理:if(p != NULL)形式 */
2) 直接访问指针变量p本身
if(p) {
}
/* 如果指针变量p为指针零值,条件表达式结果为false;否则如果指针变量p不为指针零值,条件表达式结果为true */
所谓的也指针,指的是所定义的指针所指向的是未开辟的存储空间,此时对于指针所指向空间的访问出现异常(绝大多数情况为:Segmentation fault(段错误))。
在程序中可能产生野指针的情况,然后尽可能避免野指针的产生。
在指针定义的时候,未做初始值设置。此时指针所指向的空间为随机空间,会成为野指针;
解决思路:在定义的时候完成初始值设置,在不明确具体指向空间则设置为NULL。
在程序运行过程中,可能会修改指针变量的值,从而导致指针指向空间为未开辟空间,会成为野指针;
解决思路:在程序设计的时候,减少指针变量值的修改;
在指针指向空间动态销毁过程中,会出现指针所指向空间的销毁而指针指向空间未开辟,会成为野指针;
解决思路:在指针指向空间释放的时候,将指针值设置为NULL。
对于指针的访问:
可以通过指针值来判断指针指向空间是否有开辟;
一般思路:指针值为NULL表示指针没有指向,否则表示有指向。
注意:对于也指针不能完全避免,只能尽可能减少野指针的出现。从而避免程序的异常运行。
野指针和空指针的区别:
空指针是指针零值,而野指针表示指针指向空间未开辟;
使用空指针避免减少野指针。
在C语言中,有一个特殊的数据类型,其关键字为:void。
对于void是数据类型关键字,不能用来定义数据类型变量,只能用来定义指针。所存储的指针可以是任意数据类型的指针;同时可以存储一级或者多级指针。
void指针的使用,需要根据实际应用需求,将其转换为特定数据类型的指针引用访问,如果是多级指针需要多层次的引用访问。
#include
int main()
{
int a = 123;
char ch = 'a';
int *q = &a;
// void a; /* 语法错误:void不能用来定义数据类型变量 */
void *p;
p = &a;
// printf("%d\n", *p); /* 语法错误:void类型指针,所指向空间数据类型不确定,不能直接解引用访问 */
printf("%d\n", *(int *)p);
p = &ch;
printf("%c\n", *(char *)p);
p = &q;
printf("%d\n", *(*(int **)p));
}
所谓的const关键字,可以用来修饰数据类型变量或者指针变量的定义;
所谓的const数据类型变量,指的是在变量定义的时候,使用const关键字进行修饰,此时的变量称为只读变量;变量在访问的过程中只能进行读访问,不能进行写访问(修改变量数据)。
#include
int main()
{
int a = 3;
const int b = 4;
int *p = &b; /* 实质会做指针的强制转换,将const修饰的指针强制转换为非const修饰的指针 */
printf("a = %d, b = %d\n", a, b);
a = 30;
printf("a = %d, b = %d\n", a, b);
// b = 40; /* 语法错误:const修饰的变量为只读变量,不能被(直接)修改 */
printf("a = %d, b = %d\n", a, b);
*p = 111; /* 通过指针的解引用可以间接修改空间,谨慎使用 */
printf("a = %d, b = %d\n", a, b);
}
所谓的修饰指针目标,指的是const所修饰的是指针所指向空间的数据;
const int a = 3;
const int b = 4;
const int *p; /* 指针变量定义的时候,可以初始化值 */
int const *p;
1) 指针目标空间的内容不能被修改;在指针变量所指向空间定义的时候需要设置初始化值;
*p = 123; /* 语法错误:assignment of read-only location ‘*p’ */
2) 指针指向可以被修改,也就是说指针的值可以修改;在指针变量定义的时候可以不设置初始值
p = &b; /* success */
所谓的修饰指针指向,指的是const所修饰的指针变量本身,此时指针变量的值不能被修改。也就是指针指向不能被修改。
int a = 3;
int b = 4;
int * const p = &a; /* 指针变量在定义的时候,需要设置初始化值 */
1) 指针变量p的值不能被修改,也就是指针指向不能改变;在指针变量定义的时候需要设置初始值(明确指针的指向);
p = &b; /* 语法错误:assignment of read-only variable ‘p’ */
2) 指针指向空间的内容可以被修改;在指针变量所指向空间定义的时候可以不设置初始值。
*p = 5; /* success */
const int a = 3; /* 定义指向空间数据变量,需要设置初始值 */
const int * const p = &a; /* 定义指针变量,需要设置初始值 */
int const * const p;
1) 第1个const修饰的是指针目标,也就是指针所指向空间的数据不能被修改,在定义指针所指向空间的时候需要设置初始值;
*p = 5; /* 语法错误:assignment of read-only location ‘*p’ */
2) 第2个const修饰的是指针指向,也就是指针所指向空间不能改变,在定义指针变量的时候需要设置初始值;
p = &b; /* 语法错误:assignment of read-only variable ‘p’ */