详解C语言指针我真的让C指针给我唱征服了~乌拉
Posted 大猩猩!
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了详解C语言指针我真的让C指针给我唱征服了~乌拉相关的知识,希望对你有一定的参考价值。
文章目录
前言
众所周知,C指针算是C最难的一部分。在这我会将和大家一起深究指针并征服C指针。
在正式学习进阶指针之前,我们先来简单回忆下指针基础:
指针定义:指针变量,用于存放地址。地址唯一对应一块内存空间。
指针大小:32位平台下占4个字节,64位平台占8个字节。
指针类型:类型决定指针±
整数的步长和指针解引用时访问的大小。
指针运算:指针解引用,指针±
整数,指针-指针,指针的关系运算。
指针诞生:
这些内容在C基础部分已经讲过,铁汁们可以复习一下👉传送门👈,看完记得回来啊~
理解一遍后,就让我们正式起航扬帆吧!乌拉~
注: 我们习惯把指针变量叫作指针,本文指针本质是指针变量
一、字符指针
1.字符指针的定义
字符指针: 指向字符的指针,类型为char*
2.字符指针的作用
- 指向单个字符变量
char ch = "w";
char* pch = &ch;
- 指向字符串首字符
char* pc = "hello";
printf("%s\\n",pc);
看图说话:
(为了理解简单,地址用201、204表示)
①比较好理解,
pch
存有ch
的地址,因此可通过解引用操作访问ch
②并不是把字符串"hello"
放进指针,而是把字符串首字符的地址放进指针,通过首字符地址,可以找到整个字符串
对于上面的解析,我们可以进行验证:
为什么靠一个首字符的地址就可以找到整个字符串呢?
下面我们来证明一下:
char* pc = "hello";
printf("%c\\n", *(pc + 1));//e
printf("%c\\n", *(pc + 2));//l
printf("%s\\n", pc);//hello
printf("%s\\n", pc + 1);//ello
运行结果:
- 字符串中每1个字符占1个字节,且内存的单元是1个字节,为了方便管理,字符串在内存空间上是连续存放的
即hello是连续的%s
:输出字符串,从所给地址开始,一直打印到\\0
结束符号(不包括'\\0'
)
3.字符指针的特点
这是一道题面试题:
#include<stdio.h>
int main()
char str1[] = "hello bit";
char str2[] = "hello bit";
const char* str3 = "hello bit";
const char* str4 = "hello bit";
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;
运行结果:
数组名是数组首元素的地址;指针存有字符串首字符的地址
两者有异曲同工之妙,所以
(str1 == str2) ,表示比较存放相同字符串的两个数组的地址是否相同;
(str3 == str4),表示比较存放相同字符串的两个指针的值是否相同
我们可以做出分析:
1.str1[]和str2[]是字符数组,在内存上会开辟两块地址不一样空间,但存放相同的内容
"hello bit"
2.str3和str4是指向常量字符串的指针,而常量字符串存放在内存的常量区,常量区特点是常量值不可被修改且有唯一性(就没有存在2份或多份的必要),所以指针指向的是同一份数据,故地址是相同的
图解:
注: 常量区的常量不可修改,通常用const
来修饰。防止被意外修改
二、指针数组
1.指针数组的定义
我们先看:
int arr[10]; 整型数组
char ch[5]; 字符数组
float f[20]; 浮点型数组
整型数组是存放整型的数组。
类比得指针数组是存放指针的数组。
int* parr[10];整型指针数组
char* pch[5];字符指针数组
float* pf[20];浮点型指针数组
关于指针数组的数组名:
int arr[10];
int* parr[10];
数组的数组名是首元素的地址:
整型数组的数组名arr
,是首元素(整型)的地址,所以arr
是一级指针。
整型指针数组的数组名parr
,也是首元素(整型指针)的地址,所以parr
是二级指针。
2.指针数组的使用
整型指针数组的使用:
#include<stdio.h>
int main()
//int a = 10;
//int b = 20;
//int c = 30;
//int* arr[3] = &a, &b, &c ;//不常用的写法
int arr1[] = 1,2,3,4,5 ;
int arr2[] = 2,3,4,5,6 ;
int arr3[] = 3,4,5,6,7 ;
int* parr[] = arr1,arr2,arr3 ;//常见的写法
for (int i = 0; i < 3; i++)
for (int j = 0; j < 5; j++)
//1.
printf("%d ", parr[i][j]);
//2.
// printf("%d ", *(*(parr + i) + j));
printf("\\n");
return 0;
运行结果:
通过指针数组访问整型数组的每一个元素
parr[i][j]
等价于*(*(parr + i) + j)
字符指针数组的使用:
#include<stdio.h>
int main()
const char* pch[] = "abcde", "bcdef", "cdefg" ;
for (int i = 0; i < 3; i++)
//1.
printf("%s", pch[i]);
//2.
// printf("%s", *(pch + i));
printf("\\n");
运行结果:
pch[i]
是字符串首字符的地址,%s
:打印字符串
三、数组指针
1.数组指针的定义
我们已经知道:
字符指针:指向字符的指针
整型指针:指向整型的指针
C语言语法是有规律性的,也就是说,指针指向的类型<==>指针的类型(相互决定)
所以我们可以类比得:
数组指针:指向数组的指针,即数组的地址存放在数组指针中。
到此,我要说一下,指针部分会出现一些复杂的类型,那如何理解一个复杂的类型? |
在这我先给大家抛出一个屡试不爽的技巧总结,有了这些法宝,后面的路就会容易走。
类型显得复杂的原因是它由多种的运算符组成,当我们分清运算符的优先级,理清顺序,自然会柳暗花明又一村~
1.运算符主要有三种,它们的优先级是:
* < [ ] < ( )
2.变量名第一次与运算符结合就决定了它的本质。(变量名的处女情结?_?😂)
如,先与*
结合是指针;先于[ ]
结合是数组;
( )
:1.先于( )
结合是函数,即p( )
2.用于改变优先级,如(*p)
下面我们一起来探索一下吧!
int* p[10];
int(*p)[10];
int p(int);
int (*p)(int);
int *(*p(int))[10];
int* p[10];
//由优先级知:p先于[]
结合,则p是数组;后与*
结合,则是指针数组;最后与int
结合,则是整型指针数组
int(*p)[10];
//()优先级最高,p先与*
结合,则p是指针;后[]
结合,则是数组指针;最后与int
结合,则是整型数组指针
int p(int);
//p先与( )
结合,则p是函数;参数是整型,返回值是整型,则是参数为整型,返回值是整型的函数
int (*p)(int);
//p先与*
结合,则p是指针;后与( )
结合,则指针指向的是函数,函数参数是整型,返回值是整型,则是一个指向参数为整型,返回值是整型的函数的指针(函数指针)
int *(*p(int))[10];
//p先与( )结合,则p是形参为int
的函数;后与*结合,则是返回指针的函数;再与[ ]
结合,则是返回的指针指向的是一个数组;再与*
结合,说明数组里的元素是指针;最后与int
结合,指针指向的内容是整型数据。所以p是返回值为整型指针数组指针,形参为int型的函数。
2.细说指针
好了,现在我们已经学会了如何理解一个复杂的类型。那对于一个指针,我们要搞清楚它的什么呢? |
指针是一个特殊的变量,它存放着内存中的地址。
要深入了解它从这四方面考虑:
2.1.指针类型
上一章说过,去掉名字就是类型。
同理,若把指针声明语句中的指针的名字去掉,剩下部分就是该指针的类型。
int* ptr;
//指针的类型是int*
char* ptr;
//指针的类型是char*
int** ptr;
//指针的类型是int**
int (*ptr)[5];
//指针的类型是int()[5]
int* (*ptr)[10];
//指针的类型是int(*)[10]
指针类型的意义(C基础篇已经讲过):
1.指针解引用访问几个字节(访问的内存空间大小)
2.指针类型决定了指针±整数跳过几个字节(步长)
2.2.指针所指向的类型
指针所指向的类型决定了编译器看待指针指向内存区的内容的方式
若把指针声明语句中的指针的名字和名字左边的指针声明符号*
去掉,剩下部分就是指针所指向的类型。
int* ptr;
//指针所指向的类型是int
char* ptr;
//指针所指向的类型是char
int** ptr
;//指针所指向的类型是是int*
int (*ptr)[5];
//指针所指向的类型是int()[5]
int* (*ptr)[10];
//指针所指向的类型是int()[10]
通过观察我们可以发现:
二者可互推
2.3.指针的值
指针的值:指针里存放的地址
举个例子:
int *p; //定义一个指针
int a; //定义一个int类型的变量
p=&a; //使用取址运算符(&)将变量a的地址赋给p
指针的值:p本身的值,p里存放这变量a的内存的起始地址
而指针p所指向的内存区就是从a的起始地址开始,长度为size(int)
的一片内存区。
2.4.指针大小
指针大小:32位平台下占4个字节,64位平台占8个字节。
3.数组名相关
老生常谈:
①sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。
②&数组名,取出的是数组的地址。&数组名,数组名表示整个数组。
除这两种外,数组名都是数组首元素的地址。
#include<stdio.h>
int main()
int arr[10] = 0;
int* p1 = arr;//arr是数组首元素地址,为int型
int (*p2)[10] = &arr;//&arr是整个数组的地址,为int [10]型
//arr和&arr值一样,但类型不一样
//p1和p2是相同的指向同一位置
printf("%p\\n", p1);//204
printf("%p\\n", p2);//204
//指针类型决定指针±整数的步长
printf("%p\\n", p1 + 1);//跳过一个整型,208
printf("%p\\n", p2 + 1);//跳过一个数组,244
return 0;
//为了简单理解, 204,208,244表示地址
4.数组指针的使用
当我们遍历一维数组时,可以这样做:
void Print1(int arr[], int sz)
for (int i = 0; i < sz; i++)
//printf("%d ", arr[i]);
printf("%d ", *(arr + i));
void Print2(int* arr, int sz)
for (int i = 0; i < sz; i++)
printf("%d ", arr[i]);
//printf("%d ", *(arr + i));
int main()
int arr[10] = 1,2,3,4,5,6,7,8,9,10 ;
int sz = sizeof(arr) / sizeof(arr[0]);
Print1(arr, sz);
Print2(arr, sz);
return 0;
运行结果:
把数组名传给函数时,我们有两种接收方式:
①数组接收,编译器会将数组退化为指针
②指针接收
因为数组名是数组首元素的地址,用指针接收是正确的。
当数组指针用来接收并访问一维数组时:
#include<stdio.h>
int main()
int arr[10] = 1,2,3,4,5,6,7,8,9,10;
int (*parr)[10] = &arr;//指针指向数组,数组有10个元素,每个元素为int型
int i = 0;
for(i = 0; i < 10; i++)
printf("%d ", *((*parr) + i));//*parr相当于arr
return 0;
通常这种写法显得小题大作,比较别扭,非常不推荐这种写法。
通常,数组指针用来接收并访问二维数组,会有很好的效果:
void Print1(int arr[3][5], int r, int c)//二维数组传参,用二维数组接收,实际上不会创建二维数组,编译器会将int arr[3][5]退化为int(*pa)[5]
for (int i = 0; i < r; i++)
for (int j = 0; j < c; j++)
//printf("%d ", arr[i][j]);
printf("%d ", *(*(arr + i) + j));
printf("\\n");
void Print2(int(*pa)[5], int r, int c)//二维数组传参,用数组指针接收
for (int i = 0; i < r; i++)
for (int j = 0; j < c; j++)
//1.
printf("%d ", pa[i][j]);
//2.
// printf("%d ", *(*(pa + i) + j));
printf("\\n");
int main()
int arr[3][5] = 1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7 ;
Print1(arr, 3, 5);//二维数组首元素是首行
Print2(arr, 3, 5);//二维数组首元素是首行
return 0;
运行结果:
分析:
在C基础数组章节,我们知道二维数组在内存中也是连续存储的。
在这里简单提下,需要复习的,👉C基础数组传送门 👈
连续存储:1.每一行内部的元素连续存放 2.行与行之间连续存放
- 因此,二维数组的数组名是首行的地址,类型是
int(*)[5]
- 二维数组首元素地址和数组指针是等价的,即数组指针
pa
就是数组名。- 指针类型是
int(*)[5]
,解一层引用找到的是二维数组的行- 指针所指向的类型是
int[5]
,再解一层引用找到的是某行中的元素
综上,正确的做法是:使用数组指针来接收二维数组。
正确使用数组指针会有很好的效果,但如果随便用可能会很别扭。
下面是强行使用数组指针的错误用法:
void Print3(int(*pa)[10], int sz)
for (int i = 0; i < sz; i++)
//printf("%d ", pa[i]);
printf("%d ", *(pa + i));
int main()
int arr[10] = 1,2,3,4,5,6,7,8,9,10 ;//一维数组
int sz = sizeof(arr) / sizeof(arr[0]);
Print3(&arr, sz);//&arr是整个数组的地址
return 0;
实参为整个数组的地址,形参是数组指针,*(pa+i)
解一层引用后,打印出来的为什么还是地址呢?
&
是取地址运算符,*
是间接运算符。&
和*
互为逆运算- 则
*&x
的含义是,先获取变量 x 的地址,再获取地址中的内容,相当于抵消- 所以
&arr
传给pa
,执行*pa
,相当于*&arr
,实质就是arr
,还是整个数组的地址。- 整个数组的地址的类型是
int(*)[10]
,即指针pa
的类型是int(*)[10]
- 由指针类型决定指针±整数的步长可知,每个地址间相差40字节。
四、数组传参和指针传参
写代码时要把数组和指针传递给函数的情况在所难免,那函数参数该如何设计呢? |
1.一维数组传参
一维数组传参,下面的接收方式它合理吗?
void test(int arr[])//合理吗?
void test(int arr[10])//合理吗?
void test(int *arr)//合理吗?
void test2(int *arr[])//合理吗?
void test2(int *arr[20])//合理吗?
void test2(int **arr)//合理吗?
int main()
int arr[10] = 0;
int学习C语言的指针部分有啥好的方法吗?真的很难啊!!