C语言指针对于新手的学习与理解是一大难关,指针对于C语言来说可以被称为C语言的灵魂,对于初学者来说判断是否是指针的标准在于有没有 * ,但事实上是这样的吗?或者来说还有哪些指针的类型,本文用通俗易懂的语言帮助初学者学习指针,理解指针,运用指针!
那接下来就开始吧!
目录
一、什么是指针?
1.指针的初理解?
?2.什么是指针变量?
二、指针的初阶
1.指针与指针的类型
2.指针的解引用 *
3.野指针
?3.1野指针的成因
?3.2规避野指针
三、指针进阶?
0.关于数组名(关于arr与&arr的差别等)
?1.字符指针? ?char*
?2.指针数组?
3.数组指针
?4.函数指针
?5.函数指针数组
一、什么是指针?
1.指针的初理解?
对于指针的理解要有两点。
首先,指针就是内存中的一个最小单元的编号,简单来说就是“地址”
其次,平时说的指针,通常都是指针变量,用于存放内存地址的变量
简而言之,就是内存,就是地址
?2.什么是指针变量?
理解什么是指针变量之前,我们要明白每一个值,每一个量在计算机内要存储下来都需要空间,这个空间的名称就可以叫做地址,?通过&(取地址运算符)就可以取出该量的地址。如下实例:
?
?
通过VS的监视功能,能够找到a的值是10,a的地址就是0x00effac,就是&a 是?0x00effac。
这里一定要严格区分a的值与a的地址
? ? ? ? ? ? ?a的值是int类型是10? ? ? ? ?a的地址是0x00effac 类型是int*(稍做了解)
那么用于存储a的地址的变量就是指针变量!!
所以这里的指针变量的值是某个变量的地址,指针变量的地址是自己的地址!!
实例如下:
?
?创建一个指针变量p,将a的地址赋值给p,通过监视来看,p的值就是&a?,&p就是p的地址
到此为止,对于指针已经有了一定的了解
总结一下:
指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
指针变量,也是变量!是可以改变的!只不过存储的是地址,而不是某个值! 指针是用来存放地址的,地址是唯一标示一块地址空间的。 所有指针的大小在32位平台是4个字节,在64位平台是8个字节。
?
二、指针的初阶
1.指针与指针的类型
数据类型有int float double char short long 等
那么指针是否有对应的指针类型呢?? ? ? ? ? ?答案是有的!? ? ? ? ? ? ? ?对应如下:
?
?指针变量的类型,要对应相应存储的变量的类型,存储int类型 就应用int* 来定义指针
这里可以看到,指针的定义方式是: type + *? ? 所以对应下方的指针的含义应该就能有所理解了
?其次指针类型的含义不仅仅是代表要存储量的类型,还有其他的特殊含义,演示如下:
?总结:指针的类型决定了指针向前或者向后走一步有多大(距离)。
2.指针的解引用 *
前面提到了&(取地址运算符)那么就相当于找到了房间号!那么打开房门就需要钥匙,那么就是解引用操作*
我们可以先理解*&a的含义
?
?所以对比&(取地址操作)*我们可以理解为取值操作,*的对象是地址,才能进行取值操作!
我们把&a赋给指针变量p1,所以p1就相当于&a,我们等价替换就可以得到*p1
从上面的案例就可以得到? *p1 就是??a? p1是a的地址,通过*就可以顺藤摸瓜找到 a 了!
3.野指针
????????很多初学者都从他人口中听说过野指针吧,都听闻过野指针的威力吧。如果指针可以比喻成箭头,指向的就是某变量的地址,再通过*就可以找到这个变量进行赋值等操作。那么顾名思义,野指针就是随便指?,再进行*解引用操作就会更改不确定的量,会造成很大危害!
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
?3.1野指针的成因
指针未初始化? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?实例如下:
?我想通过指针p1接收a的地址,再进行*解引用操作对a的进行赋值,但如果没有赋值给指针,那么指针所指的对象就是未知的,危险性极强!
如果我们想定义一个指针,但是现在还不知道所指的对象,那么我们可以先进行NULL赋值
?指针越界? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 实例如下:
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++){
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
?3.2规避野指针
?1. 指针初始化 2. 小心指针越界 3. 指针指向空间释放即使置NULL 4. 避免返回局部变量的地址//调用函数时避免返回局部变量的地址 5. 指针使用之前检查有效性
#include <stdio.h>
int main()
{
int *p = NULL;
//....
int a = 10;
p = &a;
if(p != NULL)//检查指针有效性
{
*p = 20;
}
return 0;
}
综上:我们已经学习过了什么是指针,什么是指针变量,指针变量的类型,指针变量的两层含义,&取地址运算符,*解引用运算符,什么是野指针,野指针的成因,如何规避野指针?。了解这么多指针的初阶应用就学习的差不多了,接下来是指针更高阶的学习,更难的操作,更多头脑风暴!
你准备好了吗!Are you ready?
?被?指到的人都能学会!??
三、指针进阶?
0.关于数组名(关于arr与&arr的差别等)
在进一步深入学习指针之前,重温一下关于数组名的小知识,有时数组名代表数组首元素的地址,有时又表示整个数组,经常会混乱,在这里进行小小总结!
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
?先问自己一个问题?? ? 你能区分arr与&arr的差别吗?? ?
通过以上代码会发现结果是一致的,但这俩的含义真的是一样的吗?我们再演示以下代码
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("arr = %p\n", arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr = %p\n", &arr);
printf("&arr+1 = %p\n", &arr+1);
return 0;
}
?通过以上代码可以发现:
arr+1跳过的是一个int的长度 而&arr+1跳过的是整个数组的长度就是int*10的长度
arr真实含义表示的是arr[0]的地址,就是整个数组的第一个元素的首地址
&arr真实含义表示的是整个数组的地址
那有没有例外呢? 答案是有的!不过也不多 只需要记住如果sizeof(数组名)得到的是整个数组的长度即可
小结:关于数组名
除了 sizeof(数组名) 表示整个数组的长度,其他时候的数组名表示 数组首元素的地址。
数组名+1 跳过数组的第一个元素
&数组名 表示整个数组的地址
&数组名+1 跳过整个数组的地址?
?1.字符指针? ?char*
在指针的类型中我们知道有一种指针类型为字符指针 char* ;
一般用法:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
char ch = 'w';
char* p = &ch;
*p = 'm';
return 0;
}
?除此之外,char* 字符指针还有一种使用方法
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
const char* pstr = "hello cainiao.";//这里是把一个字符串放到pstr指针变量里了吗?
printf("%s\n", pstr);
return 0;
}
?有很多同学会把 const char* pstr = "hello cainiao."; 这个代码理解为 把hello cainiao.放在了pstr里
实际上是把首元素的地址 h 存放到了 指针pstr 里!
为了帮助更好的理解const char* 演示以下代码!?
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
char str1[] = "hello cainiao.";
char str2[] = "hello cainiao.";
const char* str3 = "hello cainiao.";
const char* str4 = "hello cainiao.";
if (str1 == str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if (str3 == str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
?有没有发出灵魂拷问???what?
?关于第一个if()应该很好理解
即便两个字符数组的内容是一致的,但是比较字符数字的内容不能用==来比较(要用strcmp),这里==比较的是地址,str1与str2表示的是首元素地址,两个字符数组,地址必然不一样,所以肯定不相等
关于第二个if()?
通过以上代码会发现,两个常量字符数组所指的地址居然是一样的???
0.这里str3和str4指向的是一个同一个常量字符串。
1.实际上C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。
2.但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。
?所以如果str3与str4所指的是不同常量字符串,那么就不会相同,再简单测试一下。
?了解char*,const char* 的区别以及常量字符串在内存的存放就可以了!?
?2.指针数组?
?首先玩个文字游戏,能否区分指针数组还是数组指针的中心词。
指针数组的理解要对比整型数组,浮点型数组,字符数组......,所以中心词是数组
对比理解的话,可以清晰的得出指针数组是一个数组,数组存放的数据类型是指针!
?对比定义指针数组
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
char str1[] = "hello cainiao.";//字符数组
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };//整型数组
float b[10] = { 1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0 };//浮点型数组
//......
char* pstr[10]; //字符指针数组
int* pint[10]; //整型指针数组
float* pf[10]; //浮点型指针数组
//......
return 0;
}
?指针数组的应用
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
int a = 1;
int b = 1;
int c = 0;
int d = 1;
int e = 1;
int f = 9;
int* pint[6] = {&a,&b,&c,&d,&e,&f}; //整型指针数组
for (int i = 0; i < 6; i++) {
printf("%d", *pint[i]);
}
return 0;
}
3.数组指针
数组指针的理解就要对比整型指针,浮点型指针,字符指针......中心词是指针
所以数组指针就是指向数组的指针,就是一个指针存放了数组的地址!
int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?
?int *p1[10];? ? ? 指针数组
*的优先级比较低所以p1先与[]结合再与 * 结合,所以 int* 是数组里存放内容的类型 就是指针 int (*p2)[10];? ? ?数组指针
在()先与*结合,所以p2就是一个指针,所指向的类型就是int (*)[10]
?对比定义数组指针
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
int* a ;//整型指针
float* b ;//浮点型指针
double* c ;
char* d ;//字符指针
//......
int arr1[6] = { 1,2,3,4,5,6 };
float arr2[6] = { 1.0,2.0,3.0,4.0,5.0,6.0 };
char arr3[6] = { 'n','i','h','a','o','\0' };
int(*pint)[6] = &arr1;//整型数组指针 类型是int(*)[6]
float(*pf)[6] = &arr2;//浮点型数组指针 类型是float(*)[6]
char(*pch)[6] = &arr3;//字符数组指针 类型是char(*)[6]
//......
return 0;
}
?数组指针,是用来接收数组的地址,所以一定要加上&取地址运算符,如果不加,就是取的是数组首元素的地址,会出大错误,要注注注意!
?数组指针的应用
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int(*arr)[5], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
print_arr1(arr, 3, 5);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
print_arr2(arr, 3, 5);
return 0;
}
?4.函数指针
?指针是用来存储地址的,那么有函数指针就可以看出每一个函数也是有地址的!
先看一段代码
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
?
所以来说函数名和&函数名都表示函数的地址?
函数指针的定义应用
#include <stdio.h>
int add(int x,int y)
{
return x + y;
}
int main()
{
int (*p)(int, int) = add;
printf("%d", p(1, 2));
return 0;
}
?int (*p)(int, int) = add;
函数指针p的类型是int(*)(int,int)? ?第一个int是返回类型,(int,int)是形参列表
?5.函数指针数组
int Add(int x,int y) {
return x + y;
}
int Sub(int x, int y) {
return x - y;
}
int Mul(int x, int y) {
return x * y;
}
int Diy(int x, int y) {
return x / y;
}
int main() {
int (*pfArr[])(int, int) = { 0, Add, Sub, Mul, Diy };//函数指针数组
printf("%d\n", (pfArr[1])(2, 4));
printf("%d\n", (pfArr[2])(2, 4));
printf("%d\n", (pfArr[3])(2, 4));
printf("%d\n", (pfArr[4])(2, 4));
return 0;
}
?
总结
以上就是今天要讲的内容,本文仅仅简单介绍了指针的使用,有指针初阶,指针的高阶,希望同学们各取所需,指针的学习不能止步于看看文章,更重要的要去实践,码友们一起加油吧!
|