C语言:指针
这是很基础的教程,我只是写给自己看,作为一个学习笔记记录一下,如果正在阅读的你觉得简单,请不要批评,可以关掉选择离开
如何学好一门编程语言
- 掌握基础知识,为将来进一步学习打下良好的基础。
- 上机实践,通过大量的例题学习怎么设计算法,培养解题思路。
- 养成良好的编码习惯,注释一定要写,要不然保你一周后自己写的代码都不认识了。
每一个变量都有一个内存地址,而内存地址可由 & 变量名 方式访问
#include <stdio.h> int main () { int var = 10; // 声明定义初始化 int *p; // 声明定义指针变量 p = &var; // 访问var_runoob的地址,赋值给指针变量p printf("var 变量的地址: %p\n", p); // 000000000061FE14 return 0; }
指针定义
指针 就是 内存地址,指针变量 就是是用来 存储内存地址 的 变量。
同样的,我们使用指针存储变量地址之前,需要对指针进行声明。(C语言的特色,凡事需先声明)
注意:指针的类型 必须与 变量的类型 一致,整型指针只能指向整型变量的地址
指针声明
指针类型 *指针变量名; int *ip; /* 声明整型 指针变量ip */
指针赋值
int var = 20; int *ptr; // 声明 指针变量 ptr = &var; // 把ptr 指向var的地址
&是地址运算符,ptr和&var的区别在于,ptr是变量,而&var是常量。
可以通过间接运算符 *,找到指针指向的值。
printf("%d", *ptr); // 20
和所有变量一样,指针变量也有自己的地址和值。指针变量的值 是它指向的地址,指针变量的地址可以通过 &运算符得到。
int urn=1; int *ptr=&urn; printf("%d, %p \n",urn,&urn); // 1, 0x7fffe5a1e87c printf("%p, %p \n", ptr, &ptr); // 0x7fffe5a1e87c, 0x7fffe5a1e880
指针加1,指针的值递增它所指向类型的大小(以字节为单位)。
#include <stdio.h> #define SIZE 2 int main(void) { short dates[SIZE]={1,2}; // short 是2字节 short * p; short index; p = dates; // 把数组地址赋给指针 for (index = 0; index < SIZE; index++) printf("指针 + %d: %10p, %d\n", index, p + index, *(p + index)); // 指针加1,地址+2, 但是取的值是下一个 // 指针 + 0: 0x7fffe206c274, 1 // 指针 + 1: 0x7fffe206c276, 2 return 0; }
C 中的 NULL 指针
在指针变量声明的时候,如果没有确切的地址可以赋值,赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。
NULL 指针是一个定义在标准库中的值为0的常量。请看下面的程序:
#include<stdio.h> int main() { int* ptr = NULL; //声明定义初始化一个指针变量 printf("ptr指向的地址是:%p\n", ptr); //00000000 return 0; }
指针变量赋0值和不赋值是不同的,指针变量未赋值时,不能使用;而赋0值,可以使用,只是不指向具体的变量。
指向指针的指针
指向指针的指针 int **var; 第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
案例
#include <stdio.h> int main() { int V=100; // 声明定义初始化一个变量V int* Pt1; // 声明一个指针变量Pt1 int** Pt2; // 声明一个指向指针的变量Pt2 Pt1 = &V; // 将 变量V 的地址赋值给 指针变量Pt1 Pt2 = &Pt1; //将 指针变量Pt1 的地址赋值给 指针变量Pt2 printf("var = %d\n", V); // 100 printf("Pt1 = %p\n", Pt1); // 00B9FD10 (指向变量V的地址) printf("*Pt1 = %d\n", *Pt1); // 100 (指向地址的变量值) printf("Pt2 = %p\n", Pt2); // 00B9FD04 (指向指针的地址) printf("**Pt2 = %d\n", **Pt2); // 100 (指向地址的变量值 的 地址变量值) return 0; }
指针运算
指针其实是一个用数值表示的地址,指针可以进行4种算术运算: ++、--、+、- ,以 ptr++ 为例, ptr 每增加一次,他就会指向下一个整数位置,即当前位置往后移4字节
递增一个指针
#include<stdio.h> const int MAX = 3; // 定义一个常量 int main() { int var[] = { 10,100,200 }; //声明并初始化一个数组 int i, * ptr; // 声明一个变量i,和一个指针变量ptr // ptr指向的是一个地址 ptr = var; // 将数组名赋值给一个指针变量(数组名就是第一个元素的地址) for (i = 0; i < MAX; i++) { printf("存储地址:var[%d] = %p\n", i, ptr); printf("存储值:var[%d] = %d\n", i, *ptr); ptr++; /* 指向下一个位置 */ } return 0; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
存储地址:var[0] = 0096FE24 存储值:var[0] = 10 存储地址:var[1] = 0096FE28 存储值:var[1] = 100 存储地址:var[2] = 0096FE2C 存储值:var[2] = 200
递减一个指针
#include<stdio.h> const int MAX = 3; // 定义一个常量 int main() { int var[] = {10,100,200}; //初始化一个数组 int i, * ptr; ptr = &var[MAX - 1]; //数组的最后一个地址赋值给指针变量ptr for (i = MAX; i > 0; i--) { printf("存储地址:var[%d] = %p\n", i - 1, ptr); printf("存储值:var[%d] = %d\n", i - 1, *ptr); ptr--; /* 指向下一个位置 */ } return 0; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
存储地址:var[2] = 0118FACC 存储值:var[2] = 200 存储地址:var[1] = 0118FAC8 存储值:var[1] = 100 存储地址:var[0] = 0118FAC4 存储值:var[0] = 10
指针的比较
只要变量指针 所指向的地址小于或等于数组的最后一个元素的地址 &var[MAX - 1],则把变量指针进行递增:
#include <stdio.h> const int MAX = 3; int main() { int var[] = { 10, 100, 200 }; //声明并初始化一个数组 int i, * ptr; //声明一个变量和一个指针变量 ptr = var; // 指针中第一个元素的地址 i = 0; while (ptr <= &var[MAX - 1]) { printf("存储地址:var[%d] = %p\n", i, ptr); printf("存储值:var[%d] = %d\n", i, *ptr); /* 指向上一个位置 */ ptr++; i++; } return 0; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//存储地址:var[0] = 00B7F864 //存储值:var[0] = 10 //存储地址:var[1] = 00B7F868 //存储值:var[1] = 100 //存储地址:var[2] = 00B7F86C //存储值:var[2] = 200
指针与数组
指针指向数组
数组比较特别,因为 数组名是一个指向数组第一个元素的指针(即数组名代表第一个元素的地址),例:
double balance[50];
balance 是一个指向 &balance[0] 的指针,即数组 balance 的第一个元素的地址。因此,下面的程序片段把 balance 的第一个元素的地址 赋值给 p :
double *p; // 声明一个指针 double balance[10]; // 声明一个数组 p = balance; // 将数组中第一个元素的地址 赋值 给 指针变量p
因此我们也可以通过 *(balance + 4) 访问 balance[4] ,这种方式反而更加常用。下面的就是指针取变量的案例:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> int main(){ /* 初始化 带有 5 个元素的整型数组 */ double balance[5] = { 1000.0, 2.0, 3.4, 17.0, 50.0 }; double* p; //声明指针p int i; p = balance; //将balance数组的第一个地址赋值给p /* 输出数组中每个元素的值 */ printf("使用指针的数组值\n"); for (i = 0; i < 5; i++){ printf("*(p + %d) : %f\n", i, *(p + i)); } printf("使用 balance 作为地址的数组值\n"); for (i = 0; i < 5; i++){ printf("*(balance + %d) : %f\n", i, *(balance + i)); } return 0; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
使用指针的数组值 *(p + 0) : 1000.000000 *(p + 1) : 2.000000 *(p + 2) : 3.400000 *(p + 3) : 17.000000 *(p + 4) : 50.000000 使用 balance 作为地址的数组值 *(balance + 0) : 1000.000000 *(balance + 1) : 2.000000 *(balance + 2) : 3.400000 *(balance + 3) : 17.000000 *(balance + 4) : 50.000000
另外字符串是特殊的字符数组(结尾的最后一个字符是"\0"),因为指针是指向地址的,字符数组的一个字符占一个地址,所以当指针变量 指向 字符串变量地址时,其实指针变量指向的是字符串第一个字符的地址。除此之外数组名也代表第一个数组变量的地址。
指针数组
定义:包含 指针变量 的 数组
类型说明符 (*指针变量名)[长度]
// 类型 *指针变量名[长度] // 也可以不用括号 int (* pax)[2]; // pz指向一个内含两个int类型值的数组
[]的优先级高于*,先与pax结合,所以pax成为一个内含两个元素的数组。然后*表示pax数组内含两个指针。
案例:声明一个由 MAX 个 整形指针 组成的数组 ptr
int main(void) { int zippo[4][2] = {{2, 4}, {6, 8}, {1, 3}, {5, 7}}; int(*pz)[2]; //声明指针数组,可以存放2个指针 pz = zippo; printf(" pz = %p, pz + 1 = %p\n", pz, pz + 1); // zippo = 0x7fffd15143c0, zippo + 1 = 0x7fffd15143c8 printf("pz[0] = %p, pz[0] + 1 = %p\n", pz[0], pz[0] + 1); // zippo[0] = 0x7fffd15143c0, zippo[0] + 1 = 0x7fffd15143c4 printf(" *pz = %p, *pz + 1 = %p\n", *pz, *pz + 1); // *zippo = 0x7fffd15143c0, *zippo + 1 = 0x7fffd15143c4 printf("pz[0][0] = %d\n", pz[0][0]); // zippo[0][0] = 2 printf(" *pz[0] = %d\n", *pz[0]); // *zippo[0] = 2 printf(" **pz = %d\n", **pz); // **zippo = 2 printf(" pz[2][1] = %d\n", pz[2][1]); // zippo[2][1] = 3 printf("*(*(pz+2) + 1) = %d\n", *(*(pz + 2) + 1)); // *(*(zippo+2) + 1) = 3 return 0; }
指针数组并不一定是存放地址,也可以存放字符
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> int main() { // const char*表示的是字符串,string const char *name[]={"A","B","C"}; // 字符串数组 for (int i = 0; i < 3; i++) { printf("names[%d]=%s\n",i,name[i]); } /*names[0]=A names[1]=B names[2]=C*/ return 0; }
指针与函数
指针传入函数
指针传入函数,其实就是地址传入函数,这时 形参需要声明为指针类型
#include <stdio.h> #include <time.h> // 函数使用前需先声明 void getSeconds(unsigned long* par); int main() { unsigned long sec; // 声明局部变量sec getSeconds(&sec); //把sec的地址 传给 函数getSeconds printf("秒速: %ld\n", sec); // 1626178397 return 0; } void getSeconds(unsigned long* par) { // 用指针变量*par 接 sec的地址 ==> par = &sec; /* 获取当前的秒数 */ *par = time(NULL); // *par 指向地址的变量值 }
保护指针数据
当给函数传参时,要选择是 传递值 还是 传递指针。通常都是直接传递数值,只有程序需要在函数中改变该数值时,才会传递指针。对于数组别无选择,必须传递指针,因为这样做效率高。但是传递地址 在函数中 容易更改原始值。如果不想该原始值被更改 可以对形参使用const。
int sum(const int ar[], int n); int sum(const int ar[], int n) /* 函数定义 */ { int i; int total = 0; for( i = 0; i < n; i++) total += ar[i]; return total; }
const告诉编译器,该函数不能修改ar指向的数组中的内容,使用const并不是要求原数组是常量,而是该函数在处理数组时将其视为常量,不可更改。
补充知识:把const数据或非const数据的地址初始化为指向const的指针或为其赋值是合法的
double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5}; const double locked[4] = {0.08, 0.075, 0.0725, 0.07}; const double * pc = rates; // 有效 pc = locked; //有效
函数返回指针
这个时候我们需要 声明函数返回类型为指针 ,函数返回指针就是返回地址。
int * myFunction() {...}
补充知识:C语言不支持调用函数时返回局部变量的地址,除非定义局部变量为 static 变量。
#include <stdio.h> #include <stdlib.h> #include <time.h> int* getRandom() { /* getRandom是一个返回指针的函数 功能:返回随机数的函数 */ // C 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。 static int r[10]; int i; srand((unsigned)time(NULL)); // 设置随机数种子 for (i = 0; i < 10; ++i) { r[i] = rand(); printf("r[%d] = %d\n", i, r[i]); } // 数组名是指向第一个元素的地址, return r; } int main() { int* p; // 声明一个指向整数的指针 int i; // 声明变量 p = getRandom(); // 调用函数,返回的是一个地址,用指针变量去接 for (i = 0; i < 10; i++) { printf("*(p + %d) : %d\n", i, *(p + i)); } return 0; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
3885 3046 10367 24274 2611 1269 9776 13671 30908 16058 *(p + [0]) : 3885 *(p + [1]) : 3046 *(p + [2]) : 10367 *(p + [3]) : 24274 *(p + [4]) : 2611 *(p + [5]) : 1269 *(p + [6]) : 9776 *(p + [7]) : 13671 *(p + [8]) : 30908 *(p + [9]) : 16058
指向指针的指针传入函数
还有一个特殊情况就是 指向指针的指针传入函数,哎,这个我还没弄懂
#include <stdlib.h> void getSeconds(char **buffer) { int BUF_SIZE = 1024; *buffer = malloc(BUF_SIZE * sizeof(char)); } int main() { char *buffer; getSeconds(&buffer); //把sec的地址 传给 函数getSeconds return 0; }
函数指针
定义一个指针变量A,指向函数B的函数名(函数名本身就是地址),指针变量A称为函数指针。下次我们想使用函数B的时候,就可以用函数指针A进行调用。
int (*函数指针变量A)(int, int) = 函数名B; // 声明一个函数指针 //或者 int (*函数指针变量A)(int, int); // 声明一个函数指针A A= 函数名B; // 将函数名赋值给A
讲再多理论不如看案例,上代码:声明一个函数指针变量p,指向函数max
#include <stdio.h> int max(int x, int y){ return x > y ? x : y; } int main(void){ /* p 是函数指针 */ int (*p)(int, int) = &max; // &可以省略,因为函数名本身就是一个地址 int a, b, c, d; // 声明变量 printf("请输入三个数字:"); scanf_s("%d %d %d", &a, &b, &c); /* 与直接调用函数等价,d = max(max(a, b), c) */ d = p(p(a, b), c); printf("最大的数字是: %d\n", d); return 0; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
请输入三个数字:1 2 3 最大的数字是: 3
函数调用函数
当一个函数A调用 函数B 时,函数A就为回调函数,函数A的实参需要传入函数名,形参需要为 函数指针
#include <stdlib.h> #include <stdio.h> // 回调函数 void populate_array(int* array, size_t arraySize, int (*getNextValue)(void)){ /* 参数1:实参为数组名(地址),形参需要为 指针 参数3:实参为函数名(地址),形参需要为 函数指针 */ for (size_t i = 0; i < arraySize; i++) { array[i] = getNextValue(); } } // 获取随机值 int getNextRandomValue(void) { return rand(); } int main(void) { int myarray[10]; // 数组声明 /* 数组名和函数名都是地址*/ populate_array(myarray, 10, getNextRandomValue); // 调用回调函数 for (int i = 0; i < 10; i++) { printf("%d ", myarray[i]); } printf("\n"); return 0; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
41 18467 6334 26500 19169 15724 11478 29358 26962 24464
参考
相关文章
- 浅谈API网关(API Gateway)如何承载API经济生态链
- 重磅解读:K8s Cluster Autoscaler模块及对应华为云插件Deep Dive
- 这可能是关于编程指南的最实用指南了
- 低代码开发不靠谱?看低代码开发在物联网APP开发中的应用
- 华为发布5GtoB核心网建设白皮书
- 快快使用ModelArts,零基础小白也能玩转AI!
- 聆听无声的话语:手把手教你用ModelArts实现手语识别
- 测试攻城狮必备技能点!一文带你解读DevOps下的测试技术
- 小熊派开发板实践:智慧路灯沙箱实验之真实设备接入
- 【API进阶之路】API带来的微创新,打动投资人鼓励我创业
- 华为云FusionInsight MRS:助力企业构建“一企一湖,一城一湖”
- 华为云专家私房课:视频传输技术选型的三大法宝
- 为什么11·11物流一年比一年快?奥秘就在这里!
- 华为云“创原会”:40+技术精英论道云原生2.0
- 容器、Docker、虚拟机,别再傻傻分不清
- 十八般武艺玩转GaussDB(DWS)性能调优(三):好味道表定义
- 云图说|知道吗?在和你对话的那头,也许是个机器人哦~
- 专利申请其实并不难?四步教你玩转专利申请!
- 玩转华为云开发|老板万万没想到:刚入职的我一人就搞定人脸识别开发
- 人少钱少需求多的新项目该怎么带?看到这篇我心里有底了!