摘要:在语言中,允许用指针变量来存放指针,因此,一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。指针详解一基础知识在语言中,定义变量时,如果在变量名前加上一个,那么这个变量就变成了对应变量类型的指针变量。
指针,是C语言中的一个重要概念及其特点,也是掌握C语言比较困难的部分。作为一个C语言初学者,我对指针也有了一定的了解,正好这几天在做一个C语言指针的知识点汇总,于是就有了这篇文章,向大家分享一些我的见解,与大家一起共勉。
在计算机中,每一个数据都是存放在储存器中的,而不同的数据类型所占的内存空间不同(例如:int类型占用4个字节,double类型占用8个字节等等),内存空间又是以字节为单位的,每一个字节又对应了一个编号,这个编号我们称为这一个内存单元的地址。
而系统在内存中又为变量分配了存储空间的首个字节单元的地址即变量的地址。为了方便用户对存储空间进行正确的访问,指针便应运而生了。
指针相对于一个内存单元来说,指的是单元的地址,该单元的内容里面存放的是数据。在 C 语言中,允许用指针变量来存放指针,因此,一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。
指针变量是存放一个内存地址的变量,不同于其他类型变量,它是专门用来存放内存地址的,也称为地址变量。定义指针变量的一般形式为:类型说明符*变量名。
在C语言中,定义变量时,如果在变量名前加上一个 “ * ”,那么这个变量就变成了对应变量类型的指针变量。
对于一个指针变量,我们需要让它来保存其他变量的地址的时候,就需要用 到 &运算符。
下面举个例子:
例1
#include int main(){ int num = 10;//在内存中开辟一块空间 int* p = #//这里我们对变量num取地址,使用了&运算符 //将num的地址存放在p变量中,p就是一个指针变量。 return 0;}
&num就取得了num的地址,指针p指向的num的地址,就形成了一个简单的指针变量。
但是对于某些特殊情况,我们可以不需要用&运算符,例如:数组,函数等等,我们后边会讲到。
上面我们了解了如何取得一个数据的地址,那么接下来我们可以尝试运用指针来解地址,从而得到这个变量的内存数据。
在指针前加一个“ * "即解引用地址,也就是从指针指向的内存中,取出这段内存地址对应的数据。
输出例1中的指针p:
例2
#include int main(){ int num = 10; int* p = # printf("%d", *p); return 0;}
运行结果为:10
指针就是一个变量,用来存放地址的变量。(存放在指针中的值,均会被看作地址)
一个小的单元会有多大:(一个字节)
那么地址是如何编写的呢?
目前经过仔细的技术及思考后,最合适的结论为:一个字节对应了一个地址。
对于32位机器,可以看作有32根地址线,每一根地址线在寻址的时候都会产生一个电信号(正电1/负电0),所以它的地址从000……000(32个0)到111……111(32个1)共有2的32次方个地址。
在32位机器上,地址是32个0或1组成的二进制序列,一个地址需要用4个字节的空间来存储,所以一个指针变量的大小就是4个字节。
同样,可以类比推理得到:64位机器有2的64次方个地址,一个指针变量的大小位8个字节。
我们来做一个练习:
例3
#include int main(){ printf("%d/n", sizeof(char*)); printf("%d/n", sizeof(short*)); printf("%d/n", sizeof(double*)); printf("%d/n", sizeof(int*)); return 0;}
运行结果为:
4
4
4
4
总结
(1)指针是一个变量,它存放的是地址。
(2)指针在32位机器中占4个字节,在64位机器中占8个字节,与类型无关。
我们前边提到,指针的定义方式是:type + * :
char* 类型的指针是为了存放char类型变量的地址;
int* 类型的指针是为了存放int类型变量的地址;
short* 类型的指针是为了存放short类型变量的地址;
double* 类型的指针是为了存放double类型变量的地址。
指针的类型决定了指针向前或者向后的空间有多大。
指针类型决定了指针进行解引用操作的时候,能够访问空间的大小:
(1) int* p:* p能够访问4个字节。
(2)char* p:* p能够访问1个字节。
(3)double* p: *p能够访问8个字节。
指针类型的意义
先来看一个例子:
例4
#include int main(){ int a = 10; char* p1 = (char*)&a; int* p2 = &a; printf("%p/n", &a); printf("%p/n", p1); printf("%p/n", p1+1); printf("%p/n", p2); printf("%p/n", p2+1); return 0;}
输出的结果为:
003CFC80
003CFC80
003CFC81
003CFC80
003CFC84
我们可以看到不同类型的指针p1和p2都指向了变量a的地址,但是p1+1指向的是003CFC81,而p2+1指向了003CFC84。一个地址向后移动了1,一个地址向后移动了4。
这是因为:指针的类型决定了指针向前或者向后移动一步有多大的距离。
(1)概念:
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
(2)成因:
①指针未初始化
例5
#include int main(){ int* p;//局部变量指针未初始化,默认未随机值。 *p = 10; return 0;}
②指针越界访问
例6
#include int main(){ int arr[10] = { 0 }; int* p = arr; int i = 0; for (i = 0; i <= 11; i++) { //当指针指向的范围超出数组arr的范围时,p就是野指针 *(p++) = i; } return 0;}
③指针指向的空间释放
例7
#include int* func(){ int a = 10; return &a;}int main(){ int* p = func(); *p = 20; return 0;}
这里的func函数虽然确实返回了地址,而p也确实接受到了返回的地址,但是当返回的时候,已经来不及保存了,因为func函数一结束,函数申请的内存等等就返回给操作系统了,已经无法再通过指针p去访问a的地址了,后边再用*p=20去访问的是已经被释放的a的地址。
(3)如何避免野指针
①指针初始化
例8
#include int main(){ int a = 10; int* p1 = &a; int* p2 = NULL;//NULL:用来初始化指针的,给指针赋值。 return 0;}
②小心指针越界
③指针指向空间释放即设置NULL
④指针使用之前检查有效性
例9
#include int main(){ int* p = NULL; int a = 10; p = &a; if (p != NULL) { *p = 20; } return 0;}
(1)指针±整数
例10:输出数组的每一个元素
#include int main(){ int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); int* p = arr; for (i = 0; i < sz; i++) { printf("%d ", *(p + i)); } return 0;}
运行结果为:1 2 3 4 5 6 7 8 9 10
(2)指针-指针
指针-指针得到的是中间的元素个数。(注意尽量大-小)
易错点提示:两个指针不能进行加法运算,这是非法的!!!两个指针在进行减法运算时,类型要相同,否则结果不可预知!!!
例11
#include int main(){ int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* p1 = &arr[0]; int* p2 = &arr[9]; printf("%d/n", *p2 - *p1); return 0;}
运行结果为:9
我们在之前学过了用递归和迭代两种方法来实现strlen函数,那么今天我们就学会了第三种方法——指针相减法:
例12:自己的strlen函数(指针相减法)
#include int my_strlen(char* str){ char* ret = str; while (*str != "/0") { str++; } return str - ret;}int main(){ char arr[] = "abcdef"; int len = my_strlen(arr); printf("%d", len); return 0;}
(3)指针的关系运算——"<" “>” “<=” “>=” “==” “!=”
指针进行关系运算的前提是它们都指向同一个数组中的元素。
易错点提示:指针的关系运算是相同类型的指针之间的关系运算,不同类型的指针之间的关系运算没有意义,指针与非0整数的关系运算也没有意义。
例13
#include int main(){ int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = &arr[0]; int* q = &arr[9]; p < q; //当p所指的元素在q所指的元素之前时,表达式的值为1;反之为0。 p > q; //当p所指的元素在q所指的元素之后时,表达式的值为1;反之为0。 p == q; //当p和q指向同一元素时,表达式的值为1;反之为0。 p != q; //当p和q不指向同一元素时,表达式的值为1;反之为0。 printf("%d %d %d %d", p < q, p > q, p == q, p != q); return 0;}
运行结果为:1 0 0 1
例14
#include int main(){ int a = 10; int* p1 = &a; int** p2 = &p1;//p2就是二级指针。 printf("%d/n", **p2); **p2 = 20;//二级指针p2改变,其指向的a随之改变。 printf("%d/n", **p2); printf("%d/n", a); return 0;}
运行结果为:
10
20
20
指向字符型数据的指针变量。每个字符串在内存中都占用一段连续的存储空间,并有唯一确定的首地址。即将字符串的首地址赋值给字符指针,可让字符指针指向一个字符串。
下面举一个例子:
例15
#include int main(){ char arr[] = "abcdef"; char* p1 = arr; printf("%s/n", arr); printf("%s/n", p1); //这里的字符指针p1指向的是arr[]中首位的地址,所以在打印时不用解引用,它表示的是从arr[]的首位开始打印至"/0"停止。 char* p2 = "abcdef";//"abcdef"是一个常量字符串。 printf("%c/n", *p);//说明p存的只是首元素a的地址。 printf("%s/n", p);//同上 return 0;}
运行结果为:
abcdef
abcdef
a
abcdef
易错点提示:在常量字符串前加一个“const”。
例16:常量字符串
#include int main(){ const char* p = "abcdef"; //*p = "w"; printf("%s/n", p); return 0;}
运行结果为:abcdef
有了const以后,我们就不能对指针p进行赋值修改了,这样可以使其更加安全的储存数据。
例17:经典易错题
#include int main(){ char arr1[] = "abcdef"; char arr2[] = "abcdef"; char* p1 = "abcdef"; char* p2 = "abcdef"; if (arr1 == arr2) printf("1/n"); else printf("0/n"); if (p1 == p2) printf("1/n"); else printf("0/n"); return 0;}
运行结果为:
0
1
这是因为arr1和arr2是分别开辟的内存,虽然元素相同,但所处内存空间不同,所以arr1 != arr2;但是字符指针p1和p2都指向了字符串"abcdef",指向相同,所以p1 == p2。
数组名表示的是数组首元素的地址。
例18
#include int main(){ int arr[10] = { 0 }; printf("%p/n", arr);//arr:首元素的地址。 printf("%p/n", arr + 1); printf("%p/n", &arr[0]);//&arr[i]:数组中对应元素的地址。 printf("%p/n", &arr[0] + 1); printf("%p/n", &arr);//&arr:整个数组的地址。 printf("%p/n", &arr + 1); //1. &arr — &数组名:数组名不是首元素地址,数组名表示整个数组,&数组名-取出的是整个数组的地址。 //2.sizeof(arr) — sizeof(数组名):数组名表示的整个数组,sizeof(数组名)计算的是整个数组的大小。 return 0;}
运行结果为:
00B3F828
00B3F82C
00B3F828
00B3F82C
00B3F828
00B3F850
也就是说,对于上述代码,arr与&arr[0]通过加1运算后发现地址加了4,而&arr通过加1运算后发现地址加了40,从而说明了&arr表示的是整个数组的地址。
概念:数组元素全为指针变量的数组称为指针数组。
也就是指针数组是一个数组,用来存放指针的数组。
如:*p[10]是一个指针数组。
举一个例子:
例19
#include int main(){ int arr1[] = { 1,2,3,4,5 }; int arr2[] = { 2,3,4,5,6 }; int arr3[] = { 3,4,5,6,7 }; int* arr[] = { arr1,arr2,arr3 }; int i = 0; //分别遍历出arr1,arr2,arr3。 for (i = 0; i < 3; i++) { int j = 0; //分别遍历出arr1,arr2,arr3中的每个元素。 for (j = 0; j < 5; j++) { printf("%d ", *(arr[i] + j)); } printf("/n"); } return 0;}
运行结果为:
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7
概念:指的是数组名的指针,即数组首元素地址的指针。
也就是数组指针是一个指针,指向数组的指针。
如:(*p)[10]是一个数组指针。
举一个例子:
例20
#include void print1(int arr[3][5], int x, int y)//二维数组打印。{ int i, j = 0; for (i = 0; i < x; i++) { for (j = 0; j < y; j++) { printf("%d ", arr[i][j]); } printf("/n"); }}void print2(int (*p)[5], int x, int y)//数组指针打印{ int i = 0; for (i = 0; i < x; i++) { int j = 0; for (j = 0; j < y; j++) { printf("%d ", *(*(p+i)+j)); } printf("/n"); }}int main(){ int arr[3]
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/123067.html
目录 前言 一、 什么是指针? 引例 计算机是怎么对内存单元编号的呢? 内存空间的地址如何得到 想存地址怎么办? 本质目的不是为了存地址 二、指针和指针类型 为什么有不同类型的指针 1.指针的解引用 2.指针+-整数 三、野指针 造成野指针的原因 1.未主动初始化指针 2.指针越界访问 3.指针指向的空间释放 规避野指针 四、指针运算 1.指针+-整数 2.指针-指针 3.指针的关系运...
摘要:这里分块讲解六函数栈帧的销毁过程一解析的作用是将栈顶的数据弹出,弹出数据储存到相应寄存器中。 ?前言? 读完这篇博客,你可以明白什么? ①局部变量到底是怎么在栈上创建的? ②为什么局部变量不初始化为随机值? ③函数是怎么传参的?传参的先后顺序是什么? ④形参和实参是什么关系? ⑤函数调用是怎...
目录 一、什么是C语言? 二、第一个C语言程序 代码 程序分析 程序运行 一个工程中出现两个及以上的main函数 代码 运行结果 分析 三、数据类型 数据各种类型 为什么会有这么多的数据类型? 计算机单位 各个数据类型的大小 注意事项 数据类型的使用 四、变量和常量 变量的分类 变量的使用 变量的作用域和生命周期 常量 五、字符串+转义字符+注释 字符串 转义字符 注释 六、选择语句 ...
阅读 2504·2023-04-26 00:57
阅读 866·2021-11-25 09:43
阅读 2174·2021-11-11 16:55
阅读 2127·2019-08-30 15:53
阅读 3524·2019-08-30 15:52
阅读 1406·2019-08-30 14:10
阅读 3309·2019-08-30 13:22
阅读 1177·2019-08-29 11:18