一、前言
你可能会有小疑问,爱什么?恨什么?
先说恨吧,相信刚学C语言的同志都恨过。
记得大一时,C语言老师上课时用的VC++6.0,记得考试时用的VC++6.0,当时真的咬牙切齿,你说能不恨吗。
没有代码提示,直接劝退了!
刚考完C语言的时候,真的想过这辈子再也不碰C了。
大二下学期,学了Java,授课老师用的Eclipse,虽然老师上课手撸代码,但是我们写作业,考试起码有提示呀,顿时喜欢上了。
就这样,大学期间搞了四年的Java。
那么爱什么呢?
爱的是,我大学毕业后缺搞起C语言,做了嵌入式Linux开发,因为我非常喜欢Linux。
C语言可以直接操作硬件的,那么你可能会问,一个编程语言怎么能和硬件想关联呢?是不是很神奇。
我一开始也不知道,后来经过在公司的一段时间内接触了HDMI、DVI、VGA等信号的驱动知识才了解到,C语言是可以直接操作寄存器的。但是也不能直接操作,而是通过一些特定的协议规则,例如:I2C协议。
搞嵌入式还是挺有好处的,没有Java加班多是真的,下午下班回家、周六、周日、年假基本上不加班,也没办法加班,因为设备是在公司,你没办法拿到家里来对吧。只能回家学习了。想加班的话,还得看公司,至少我们公司965,还是挺爽的。选择生活,还是选择其他还是看自己。
即使不爱,也得爱呀,哈哈哈哈。
二、为什么学指针
- 1、因为不会,基本用法都不会;
- 2、因为很想会,必须得懂;
- 3、因为会用到,必须得学;
不会C语言的指针,你去刷个算法题,去瞧瞧。
哈哈,记得我下定决心搞嵌入式Linux开发的时候,第一次想使用C语言去刷算法题的时候,因为想提升一下自己的C语言的基础。
就打开的一个最最最简单的题:
在这里插入图片描述
给的初始代码如下:
/** * Note: The returned array must be malloced, assume caller calls free(). */ int* twoSum(int* nums, int numsSize, int target, int* returnSize){ }
我滴个乖乖,这简直了,直接头大了,没错,直接劝退了。
再来看一下Java版的:
class Solution { public int[] twoSum(int[] nums, int target) { } }
哎,好爽快呀。
你就说吧,你不会指针,你怎么去用C语言刷算法题?????
三、说明
本篇文章不长篇大论的研究指针背后的原理,因为博主还没到这地步。只是简单的总结一下我段时间学到的技巧用法等。
四、跟我一起学
(一)C语言中的*和&
引用我其他博文中的一段话
1、C语言中为什么存在&和*
C语言中大名鼎鼎的“指针”,想必你肯定听说过吧。
没错,C语言中的&和*就是为了指针而诞生的。
指针说白了就是直接/间接的操作(取/存)存储中的地址中的数据。
试想一下,如果没有&和*的存在,你可能每天都在为计算和寻找某个变量在哪里而发愁呢!
有了&和*之后,就不需要你手动的去计算内存中的地址。
2、&和*是什么?
&:取地址运算符;
*:间接寻址,也可以称为取值运算符,这样就好理解了运算符;
&的作用:如果想找到变量的地址,可以使用&(取地址)运算符。
*的作用:如果你学过链表,你经常会用到:
p->q;
那么p就是指向q的地址。
如果你学过计算机组成原理或者操作系统,里面的寄存器的寻址方式,就有间接寻址方式。
间接寻址方式:说白了就是取这个地址指向的地址的值。
如果有一个变量p,那么p就是取p指向地址的值。
3、&(取地址运算符)和*(间接寻址运算符)的使用
int i; 是变量
int *pi;是指针
int i, *pi; char c,*pc;
例子:
#include <stdio.h> int main() { int i, *pi; char c,*pc; //初始化i为10 i = 10; //初始化c为‘a’字符 c = 'a'; //把pi指向i的地址 pi = &i; //把pc指向c的地址 pc = &c; printf("i=%d;c=%c\n",*pi,*pc); //做一些基本处理 *pi = *pi + 100; printf("*pi+100=%d\n",*pi); printf("pi addr=%p;i addr=%p;pc addr=%p;c addr=%p\n",pi,&i,pc,&c); return 0; }
执行结果:
i=10;c=a *pi+100=110 pi addr=0x7ffe76034684;i addr=0x7ffe76034684;pc addr=0x7ffe76034683;c addr=0x7ffe76034683
(二)初识指针和变量
1、变量
(1)变量如何创建
zhenghui@zhlinux:~/codeProject/20210719$ cat test1.c -n 1 #include <stdio.h> 2 3 int main() 4 { 5 6 7 //定义int类型的变量 8 int i1,i2,i3; 9 //定义double类型的变量 10 double d1,d2,d3; 11 12 return 0; 13 } zhenghui@zhlinux:~/codeProject/20210719$
如上所示代码中的【数据类型 空格 变量名 ;
】轻轻松松就定义出来了1-N个变量。
(2)变量如何赋值
//初始化变量 i1 = 5; i2 = 2; i3 = 1;
公式:【变量名 = 值;
】
意思是把什么内容保存到什么变量中。
你是不是有疑问了,区区一个小小滴变量这是如何保存的呢?别急,继续看吧骚年。
(3)变量如何使用
zhenghui@zhlinux:~/codeProject/20210719$ zhenghui@zhlinux:~/codeProject/20210719$ cat test1.c #include <stdio.h> int main() { //定义int类型的变量 int i1,i2,i3; //初始化变量 i1 = 5; i2 = 2; i3 = 1; printf("i1=%d,i2=%d,i3=%d\n",i1,i2,i3); return 0; } zhenghui@zhlinux:~/codeProject/20210719$ zhenghui@zhlinux:~/codeProject/20210719$ zhenghui@zhlinux:~/codeProject/20210719$ zhenghui@zhlinux:~/codeProject/20210719$ gcc test1.c zhenghui@zhlinux:~/codeProject/20210719$ zhenghui@zhlinux:~/codeProject/20210719$ ./a.out i1=5,i2=2,i3=1 zhenghui@zhlinux:~/codeProject/20210719$
2、指针
(1)指针如何创建
//定义int类型的指针 int *p1,*p2,*p3;
(2)指针如何赋值
//定义int类型的变量 int i1,i2,i3; //初始化变量 i1 = 5; ....... //定义int类型的指针 int *p1,*p2,*p3; //赋值 p1 = &i1;
等等
请留步,这里说一下。
指针,其实就是存的地址。
不信可以使用printf大法看看:
zhenghui@zhlinux:~/codeProject/20210719$ zhenghui@zhlinux:~/codeProject/20210719$ cat test2.c #include <stdio.h> int main() { //定义int类型的变量 int i1,i2,i3; //初始化变量 i1 = 5; i2 = 2; i3 = 1; printf("i1=%d,i2=%d,i3=%d\n",i1,i2,i3); //定义int类型的指针 int *p1,*p2,*p3; //赋值 p1 = &i1; //打印 printf("p1=%p,&i1=%p\n",p1,&i1); return 0; } zhenghui@zhlinux:~/codeProject/20210719$ zhenghui@zhlinux:~/codeProject/20210719$ ./a.out i1=5,i2=2,i3=1 p1=0x7ffde7e3f364,&i1=0x7ffde7e3f364 zhenghui@zhlinux:~/codeProject/20210719$
可以看到,我用p1=&i1
,打印的结果是相同的,所以可以得到结论。
一个错误的示范:
int *p1; p1 = 10;
我在没具体学习指针的时候,就这样做过。
编译的时候:
所以:
- 指针变量只能接收某个内存中的地址;
- 只能把地址赋值给指针变量。
zhenghui@zhlinux:~/codeProject/20210719$ zhenghui@zhlinux:~/codeProject/20210719$ gcc test2.c test2.c: In function ‘main’: test2.c:23:5: warning: assignment to ‘int *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion] 23 | p2 = 123; | ^ zhenghui@zhlinux:~/codeProject/20210719$
(3)指针如何使用
这里只需要学会*
和&
就行了,其余的暂时可以不用管。
(三)指针的一些基本用法
1、指针和变量的定义
//变量定义 int i,j,k; //指针的定义 int *pi,*pj,*pk;
2、指针和变量的初始化
//变量定义 int i,j,k; //指针的定义 int *pi,*pj,*pk; //变量的初始化 i = 10; j = 100; k = 1000; //指针的初始化 pi = &i; pj = &j; pk = &k;
如果你还不知道&和*的用法和作用,可以参考我这篇:
3、指针如何作为参数
#include <stdio.h> void maxV(int a,int b,int *max) { if( a > b) { *max = a; }else { *max = b; } } int main() { //变量定义 int i,j,k; //指针的定义 int *pi,*pj,*pk; //变量的初始化 i = 10; j = 100; k = 1000; //指针的初始化 pi = &i; pj = &j; pk = &k; int max; maxV(i,j,&max); printf("最大的数是:%d\n",max); return 0; }
结果:
最大的数是:100
4、指针如何作为返回值
#include <stdio.h> void maxV(int a,int b,int *max) { if( a > b) { *max = a; }else { *max = b; } } int *maxV2(int *a,int *b) { return *a > *b ? a : b; } int main() { //变量定义 int i,j,k; //指针的定义 int *pi,*pj,*pk; //变量的初始化 i = 10; j = 100; k = 1000; //指针的初始化 pi = &i; pj = &j; pk = &k; int max; maxV(i,j,&max); printf("最大的数是:%d\n",max); printf("最大的数是:%d\n",*maxV2(&j,&k)); return 0; }
结果:
最大的数是:100 最大的数是:1000
5、案例1:完成swap函数,完成两个参数交换的功能
zhenghui@zhlinux:~/codeProject/11指针$ zhenghui@zhlinux:~/codeProject/11指针$ cat swap.c #include <stdio.h> void swap(int *p,int *q) { int temp = *p; *p = *q; *q = temp; } int main() { int a=10; int b=20; swap(&a,&b); printf("a=%d,b=%d\n",a,b); return 0; } zhenghui@zhlinux:~/codeProject/11指针$ zhenghui@zhlinux:~/codeProject/11指针$ zhenghui@zhlinux:~/codeProject/11指针$ ./a.out a=20,b=10 zhenghui@zhlinux:~/codeProject/11指针$
6、案例2:找到最大和第二大的值
题目如下:
方法一:两次循环
第一次:找出最大的值,然后记录下来最大值的位置;
第二次查找的时候把最大值的位置的数据给清空,继续寻找最大的值,那么这一次的最大的值就是第二大的。
#include <stdio.h> #define ARRAY_SIZE(array) ((int) (sizeof(array) / sizeof(array[0]) )) /* *查找最大的值和第二大的值 * */ void find_tow_largest(int a[],int n,int *largest,int *second_largest) { //1、找到最大的值 *largest = a[0]; //记录下来最大的值的位置 int maxIndex = 0; for(int i = 1;i<n;i++) { if(*largest < a[i]) { *largest = a[i]; maxIndex = i; } } //2、找第二大的值 //把最大的值变成最小的值 a[maxIndex] = -1; *second_largest = a[0]; for(int i = 1;i<n;i++) { if(*second_largest < a[i]) { *second_largest = a[i]; } "find_tow_larget.c" 54L, 863C 31,2-9 顶端 *largest = a[i]; maxIndex = i; } } //2、找第二大的值 //把最大的值变成最小的值 a[maxIndex] = -1; *second_largest = a[0]; for(int i = 1;i<n;i++) { if(*second_largest < a[i]) { *second_largest = a[i]; } } } int main() { int a[] = {1,5,2,4,7,5,8,234}; int n = ARRAY_SIZE(a); int largest,second_largest; find_tow_largest(a,n,&largest,&second_largest); printf("最大的是:%d,第二大的是:%d\n",largest,second_largest); return 0; }
执行结果:
zhenghui@zhlinux:~/codeProject/11指针$ vim find_tow_larget.c zhenghui@zhlinux:~/codeProject/11指针$ zhenghui@zhlinux:~/codeProject/11指针$ zhenghui@zhlinux:~/codeProject/11指针$ gcc find_tow_larget.c zhenghui@zhlinux:~/codeProject/11指针$ zhenghui@zhlinux:~/codeProject/11指针$ ./a.out 最大的是:234,第二大的是:8 zhenghui@zhlinux:~/codeProject/11指针$
方法2:排序
可以利用数组的排序来做,任何排序都可以。
这里选用插入排序来做。
zhenghui@zhlinux:~/codeProject/11指针$ zhenghui@zhlinux:~/codeProject/11指针$ cat find_tow_larget.c #include <stdio.h> #define ARRAY_SIZE(array) ((int) (sizeof(array) / sizeof(array[0]) )) /* *查找最大的值和第二大的值 * */ void find_tow_largest(int a[],int n,int *largest,int *second_largest) { //1、找到最大的值 *largest = a[0]; //记录下来最大的值的位置 int maxIndex = 0; for(int i = 1;i<n;i++) { if(*largest < a[i]) { *largest = a[i]; maxIndex = i; } } //2、找第二大的值 //把最大的值变成最小的值 a[maxIndex] = -1; *second_largest = a[0]; for(int i = 1;i<n;i++) { if(*second_largest < a[i]) { *second_largest = a[i]; } } } /* *使用排序查找最大的值和第二大的值 * */ void find_sort_tow_largest(int a[],int n,int *largest,int *second_largest) { //1、初始化 for(int i = 1;i<n;i++) { if(a[i-1] > a[i]) { int j = i - 1; int temp = a[i]; while(j > -1 && temp < a[j]) { a[j+1] = a[j]; j--; } a[j+1] = temp; } } *largest = a[n-1]; *second_largest = a[n-2]; } int main() { int a[] = {1,5,2,4,7,5,8,234}; int n = ARRAY_SIZE(a); int largest,second_largest; //find_tow_largest(a,n,&largest,&second_largest); find_sort_tow_largest(a,n,&largest,&second_largest); printf("最大的是:%d,第二大的是:%d\n",largest,second_largest); return 0; }
结果:
zhenghui@zhlinux:~/codeProject/11指针$ ./a.out 最大的是:234,第二大的是:8 zhenghui@zhlinux:~/codeProject/11指针$
(四)指针和数组的渊源
1、提前说明
为了更好的说明指针和数组的情况。本文假设你最起码已经会简单的使用数组了。
下文中所使用的数组都是:int a[] = {10,20,30,40,50,60,70,80,90,100}
。
如下图所示:
我们声明数组a和指针p如下:
int a[10] = {10,20,30,40,50,60,70,80,90,100}, *p;
2、数组和指针的简单运用
int a;
只是存放的一个数,数组中而是存放的一组数;
那么我们可以把某个变量的地址赋值给某个指针变量,那么数组可不可以呢?
当然是可以的:
zhenghui@zhlinux:~/codeProject/20210720$ ./a.out a[0]=1,p=10 zhenghui@zhlinux:~/codeProject/20210720$ zhenghui@zhlinux:~/codeProject/20210720$ zhenghui@zhlinux:~/codeProject/20210720$ cat test1.c #include <stdio.h> int main() { int a[10] = {10,20,30,40,50,60,70,80,90,100}, *p; p = &a[0]; printf("a[0]=%d,p=%d \n",a[0],*p); } zhenghui@zhlinux:~/codeProject/20210720$
当我们把a[0]的地址赋值给p的时候,那么就相当于p指向了数组中a[0]处这个数值的地址,如下图所示:
3、指针中的算术运算
算术运算是什么?
就是从小就会的,加法、减法、乘法、除法。
C语言中的指针支持三种格式(只有三种,只能三种)的指针算术运算。
分别是:
指针加上整数
;指针减去整数
;两个指针相减
;
下面来分别介绍一下数组中的指针怎么加减。
(1)指针加上整数
实验代码修改如下:
zhenghui@zhlinux:~/codeProject/20210720$ cat test1.c #include <stdio.h> int main() { int a[10] = {10,20,30,40,50,60,70,80,90,100}, *p; p = &a[0]; printf("a[0]=%d, *(p + 1)=%d \n",a[0], *(p+1) ); } zhenghui@zhlinux:~/codeProject/20210720$
你猜猜(*p+1)
会打印什么。
- 老王说:我猜是11;
- 老李说:我猜会报错,指针怎么能做加法呢!;
看看是谁说的对:
zhenghui@zhlinux:~/codeProject/20210720$ gcc test1.c zhenghui@zhlinux:~/codeProject/20210720$ zhenghui@zhlinux:~/codeProject/20210720$ ./a.out a[0]=10, *(p + 1)=20 zhenghui@zhlinux:~/codeProject/20210720$
居然是20。
为什么是20呢,是不是很纳闷。
现在可以尝试猜想一下。
这个*(p+1) = 20
的话,那么这个+1的操作很明摆着就是向前移动了一个位置呀,变成了a[1]。
验证一下:
zhenghui@zhlinux:~/codeProject/20210720$ zhenghui@zhlinux:~/codeProject/20210720$ cat test1.c #include <stdio.h> int main() { int a[10] = {10,20,30,40,50,60,70,80,90,100}, *p; p = &a[0]; printf("a[0]=%d, *(p + 1)=%d \n",a[0], *(p+1) ); printf("a[1] addr=%p, *(p+1) addr=%p \n",&a[1], (p+1) ); } zhenghui@zhlinux:~/codeProject/20210720$
通过打印a[1]和p+1的地址,还真的是一样的地址。那么就证明了我们的猜想,如果指针指向数组的话,那么加N就等于是向前移动了几个位置。
zhenghui@zhlinux:~/codeProject/20210720$ ./a.out a[0]=10, *(p + 1)=20 a[1] addr=0x7fff953b17b4, *(p+1) addr=0x7fff953b17b4 zhenghui@zhlinux:~/codeProject/20210720$
(2)指针减去整数
有了前面加整数的铺垫,不难想象,减去一个整数,肯定就是向后移动了几个位置。
zhenghui@zhlinux:~/codeProject/20210720$ cat test1.c #include <stdio.h> int main() { int a[10] = {10,20,30,40,50,60,70,80,90,100}, *p; p = &a[0]; //+5 p = p + 5; printf("(p + 5)之后=%d \n",*p); //-2 p = p - 2; printf("(p + 5 - 2)之后=%d \n",*p); }
运行结果如下:
zhenghui@zhlinux:~/codeProject/20210720$ ./a.out (p + 5)之后=60 (p + 5 - 2)之后=40 zhenghui@zhlinux:~/codeProject/20210720$
(3)两个指针相减
修改测试代码如下:
zhenghui@zhlinux:~/codeProject/20210720$ cat test1.c #include <stdio.h> int main() { int a[10] = {10,20,30,40,50,60,70,80,90,100}, *p, *q; p = &a[2]; q = &a[8]; printf("p=%d,q=%d,(p - q)之后=%ld \n", *p, *q,(p - q) ); printf("p=%d,q=%d,(q - p)之后=%ld \n", *p, *q,(q - p) ); } zhenghui@zhlinux:~/codeProject/20210720$
运行结果:
zhenghui@zhlinux:~/codeProject/20210720$ ./a.out p=30,q=90,(p - q)之后=-6 p=30,q=90,(q - p)之后=6 zhenghui@zhlinux:~/codeProject/20210720$
4、指针做比较
指针和指针之间也可以使用:<、>、<=、>=、==、!=来做比较。但是只有在相同的数组中做比较才有意义,因为数组中的指针做比较的话,会依赖于同一数组中的元素的相对位置。
代码修改如下:
zhenghui@zhlinux:~/codeProject/20210720$ cat test1.c #include <stdio.h> int main() { int a[10] = {10,20,30,40,50,60,70,80,90,100}, *p, *q; p = &a[2]; q = &a[8]; printf("(p > q)之后=%d \n",(p > q) ); printf("(q < p)之后=%d \n",(q > p) ); } zhenghui@zhlinux:~/codeProject/20210720$
结果:
zhenghui@zhlinux:~/codeProject/20210720$ gcc test1.c && ./a.out (p > q)之后=0 (q < p)之后=1
5、指针指向复合
这个是c99中的一个特性。
我们之前是先创建一个数组,然后用指针再指向数组的第一个元素,以此达到目的。
这样做是很不方便的,那么接下来的操作,就是可以为了简化这个操作,减少一些麻烦。
int *p1 = (int []){1,2,3,4,5,6};
我们使用的时候,也是一样用就可以了:
printf("p1 + 1 =%d \n",*(p1+1));
6、指针直接指向数组
废话不多说,直接上代码:
zhenghui@zhlinux:~/codeProject/20210720$ cat test1.c #include <stdio.h> int main() { int a[10] = {10,20,30,40,50,60,70,80,90,100}, *p, *q; p = a; printf("p+1=%d \n",*(p+1)); } zhenghui@zhlinux:~/codeProject/20210720$ zhenghui@zhlinux:~/codeProject/20210720$ gcc test1.c && ./a.out p+1=20 zhenghui@zhlinux:~/codeProject/20210720$
(五)指针实战算法题
学习完了C语言的指针,再一次信心满满的打开了
https://leetcode-cn.com/problems/two-sum/submissions/
这道算法题。
现在Ubuntu的vim写了一下:
#include <stdio.h> int* twoSum(int* nums, int numsSize, int target, int* returnSize){ int *res = (int[2]){}; for(int i = 0;i<numsSize;i++) { for(int j = 1;j<numsSize;j++) { if(i != j && (nums[i] + nums[j] == target)) { *(res) = i; *(res+1) = j; *returnSize = 2; return res; } } } return res; } int main() { int *nums = (int[]){2,7,11,15}; int numsSize = 4; int target = 9; int returnSize; int *result = twoSum(nums,numsSize,target,&returnSize); printf("returnSize=%d,result1=%d,result2=%d \n",returnSize,*result,*(result+1)); }
运行:
zhenghui@zhlinux:~/codeProject/20210720$ gcc test2.c && ./a.out returnSize=2,result1=0,result2=1 zhenghui@zhlinux:~/codeProject/20210720$
好滴很,没有毛病。
放到力扣试试:
一遍过,哈哈哈。挺开心的。
开心的是这是我用指针写过最长的C语言代码了。
开心的是第一次用指针写了一个程序。
刚好对比一下,以前我用Java写的:
不得不说C语言的优势就一下子体现出来了。
时间上的问题不是大问题,只是本次使用了两层for循环来处理的,时间会久一些。
五、结尾
现在高兴还太早,只是初出茅庐,会了皮毛,简单的使用。
C语言高级的地方在于内存的管理,如何动态的分配内存,管理内存,如何动态的分配字符串,分配数组,释放存储空间等等,还有一大把的基础知识带研究。
下次继续研究。