当前位置: 首页 > news >正文

C语言二刷指针篇

&取得变量的地址

printf("%p\n", &a);
printf("%p\n", a);
printf("%p\n", &a[0]);
printf("%p\n", &a[1]);

前三个输出相同,a[0]和a[1]之间相差4

指针就是保存地址的变量,指针里放的是别的变量的地址

p指向i代表p里面的值就是i变量的地址

int *p,q;//*p是int类型
int* p,q;//q是int类型

对于函数:

void f(int *p);

 在被调用的时候得到了某个变量的指针:

int i=0;f(&i);

在函数里面可以通过这个指针访问到外面的这个i

如何通过指针访问和操作变量

#include<stdio.h>void f(int* p);
void g(int k);int main() {int i = 6;printf("&i=%p\n", &i);f(&i);g(i);return 0;
}void f(int* p) {printf("p=%p\n", p);printf("*p=%d\n", *p);
}
void g(int k) {printf("k=%d\n", k);
}

上面的代码中,printf("&i=%p\n", &i);用于输出变量i的地址,以便观察变量i在内存中的存储位置。%p是格式化输出符号,用于输出指针(地址)类型的值。

f(&i);调用f函数,并将i的地址作为参数传递给f函数。这样f函数就可以通过指针操作来访问和修改i的值(虽然在这个f函数中没有修改i的值)。

g(i);调用g函数,并将i的值(而不是地址)作为参数传递给g函数。这样g函数会得到i的一个副本,对这个副本的任何操作都不会影响main函数中的i。

f函数接受一个整型指针p作为参数,被初始化为i的地址。

printf("p=%p\n", p);输出指针p的值,也就是i的地址。这与在main函数中输出的&i的值应该是相同的。

printf("*p=%d\n", *p);输出指针p所指向的内存位置的值。由于p指向i,所以这里输出的就是i的值,即6。

&i=000000034277F9F4p=000000034277F9F4
*p=6k=6

修改上面的代码:

#include<stdio.h>void f(int* p);
void g(int k);int main() {int i = 6;printf("&i=%p\n", &i);f(&i);g(i);return 0;
}void f(int* p) {printf("p=%p\n", p);printf("*p=%d\n", *p);*p = 26;
}
void g(int k) {printf("k=%d\n", k);
}

如果在f函数定义时将*p修改为26,那么最后k的值也会是26。

指针的使用

场景一:

交换两个变量

#include<stdio.h>void swap(int* pa, int* pb);int main() {int a = 5;int b = 6;swap(&a, &b);printf("a=%d,b=%d\n",a,b);return 0;
}
void swap(int* pa, int* pb) {int t = *pa;*pa = *pb;*pb = t;
}

场景二:

  • 函数返回多个值,某些值就只能通过指针返回
  • 传入的参数实际上是需要保存带回的结果的变量
#include <stdio.h>// 这个函数通过指针返回两个值,一个是两个数的和,一个是两个数的乘积
void cal(int num1, int num2, int* sum, int* product) {*sum = num1 + num2;*product = num1 * num2;
}int main() {int num1 = 5;int num2 = 3;int sum_result, product_result;cal(num1, num2, &sum_result, &product_result);printf("The sum of %d and %d is %d\n", num1, num2, sum_result);printf("The product of %d and %d is %d\n", num1, num2, product_result);return 0;
}

在cal函数中sum和product是指针类型的,在main函数中sum和product是整数类型的。

  • 函数返回运算的状态,结果通过指针返回
  • 常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错:-1和0
  • 但是当任何数值都是有效的可能结果时,就得分开返回
#include<stdio.h>int divide(int a, int b, int* result) {int ret = 1;if (b == 0)ret = 0;else {*result = a / b;}return ret;
}int main(void) {int a = 5;int b = 2;int c;if (divide(a, b, &c)) {printf("%d/%d=%d\n", a, b, c);}return 0;
}

上面的代码中通过指针将除法结果返回给调用者,并使用函数返回值表示运算是否成功,这种方式在处理可能出现错误的运算时比较常用。

常见错误:

指针在得到地址前不能被赋值(除了NULL),因为此时被赋的值是一个未知的地址。

如果一个指针没有被初始化(即没有被赋予一个有效的地址)就进行解引用赋值操作会导致未定义行为。

int *ptr;
*ptr = 10;  // 错误,ptr未初始化,其值是不确定的,此时进行解引用赋值可能导致程序崩溃或产生不可预测的结果

指针可以被赋予NULL值,这是一种特殊的赋值,表示指针不指向任何有效的内存地址。

int *ptr = NULL;

当要给指针赋予一个有意义的值时,这个值应该是一个已经合法分配内存的地址,比如通过malloc等内存分配函数获得的地址,或者是某个已存在变量的地址。

int num = 5;
int *ptr = &num;  // 正确,将指针ptr指向变量num的地址

从这个意义上说,不能随意将指针指向一个未知的地址,但可以将其初始化为`NULL`或者在确保地址合法性的情况下进行赋值。

指针和数组

数组变量接收数组接收到的是值(普通变量),指针的值(指针)

函数参数表中的数组实际上就是指针。

以下四种函数原型是等价的:

int sum(int *ar,int n);
int sum(int *,int);
int sum(int ar[],int n);
int sum(int [],int);

数组变量是特殊的指针

数组变量本身表达地址

int a[10];
int *p=a;//无需用&取地址

但是数组的单元表达的是变量,需要用&取地址,a==&a[0]

[]运算符可以对数组做,也可以对指针做:p[0]<==>*p

数组变量是const的指针,所以不能被赋值

指针与const

指针是const

表示一旦得到了某个变量的地址,不能再指向其他变量

int * const q = &i;//q是const
*q=26;//ok
q++;//error

比如上面的代码,q只能指向i的事实不能被改变,指针不能指向别人,指针不能被修改

所指是const

表示不能通过这个指针去修改那个变量(并不能使那个变量成为const)

const int *p=&i;
*p=26;//error(*p是const)
i=26;//ok
p=&j;//ok

i和p可以变,i可以被赋予其他值,p可以指向其他地址,但是通过p去修改i就不可以(*p是个常量),变量不能通过该指针改变

总结:const在前,不能改值;const在后,不能改地址

转换:总是可以把一个非const的值转换成const的

void f(const int* x);
int a=15;
f(&a);//ok
const int b=a;
f(&b);//ok
b=a+1;//Error!

当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改

const数组

const int a []={1,2,3,4,5,6};

数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int,所以必须通过初始化进行赋值

如果要保护数组不被函数破坏(修改数组的值),可以设置参数为const

int sum(const int a[], int length)

指针运算

	char ac[] = { 0,1,2,3 };char* p = ac;printf("p =%p\n", p);printf("p+1=%p\n", p+1);printf("*(p+1) =%d\n", *(p+1));char ai[] = { 0,1,2,3 };char* q = ai;printf("q =%p\n", q);printf("q+1 =%p\n", q+1);printf("*(q+1) =%d\n", *(q+1));

输出:

p =0000008CB1AFFA44
p+1=0000008CB1AFFA45
*(p+1) =1
q =0000008CB1AFFA84
q+1 =0000008CB1AFFA85
*(q+1) =1

上面代码中,指针加1表示让指针指向下一个变量。如果指针不是一片连续的分配空间,如数组,则这种运算没有意义。

用指针做什么:

  • 需要传入较大的数据时用参数
  • 传入数组后对数组做操作
  • 函数返回不止一个结果
  • 需要用函数来修改不止一个变量
  • 动态申请的内存

动态内存分配

在程序运行期间根据实际需要来分配和释放内存空间的一种机制。

现在根据用户输入的元素数量动态分配内存来存储一个整数数组,然后从用户获取数组元素的值

#include<stdio.h>
#include<stdlib.h>
int main() {int number;int* a;int i;printf("输入数量:");scanf("%d", &number);a = (int*)malloc(number * sizeof(int));for (i = 0; i < number; i++) {scanf("%d", &a[i]);}for (i = number - 1; i>=0; i++) {printf("%d ", a[i]);}free(a);return 0;
}

http://www.mrgr.cn/news/59643.html

相关文章:

  • 安全见闻(8)——开阔眼界,不做井底之蛙
  • 数字IC后端实现 | Innovus各个阶段常用命令汇总
  • 大学新生如何入门编程:选择语言、制定计划及避开学习陷阱
  • python subproces模块
  • 嵌入式学习-网络-Day01
  • 2024年10月-2025年5月 Oracle 19c OCM 考试安排
  • Linux基本指令(上)
  • 66AI论文:一键速写形势与政策论文,课程作业论文写作好助手
  • 查看数据库
  • leetcode hot100【LeetCode 199. 二叉树的右视图】java实现
  • 分享几个可以免费使用GPT的网站
  • 《欢乐饭米粒儿》持续热播:第四期小品笑中有思,引发观众共鸣
  • 基于Spring Boot的中小型医院网站的设计与实现源码(springboot)
  • 计算机组成原理之寻址方式、寻址方式中哪种最常用、寻址方式中哪种效率最高
  • 通过 SYSENTER/SYSEXIT指令来学习系统调用
  • XQT_UI 组件|01|颜色
  • 知识见闻 - Gearbest电商平台
  • 144. 二叉树的前序遍历 递归
  • 双子塔楼宇可视化系统:提升建筑管理与运营效率
  • 必读篇:阿里云应用与低功耗4G模组AT开发示例指南
  • 【Unity踩坑】UWP应用未通过Windows应用认证:API不支持
  • 使用Claude新功能分析数据文件
  • 图像识别的技术原理及方法
  • 【后勤&运输集装箱】集装箱损伤检测系统源码&数据集全套:改进yolo11-ODConv
  • 【时间之外】IT人求职和创业应知【18】
  • Linux:编辑器Vim和Makefile