06. C语言指针
【指针】
C语言使用数据名调用数据,数据名相当于C语言的直接寻址,直接寻址只能调用固定数据,而指针是间接寻址,指针存储了另一个数据的地址,使用指针调用数据时首先取指针存储的内存地址,之后使用此地址调用数据,使用间接寻址有如下几点优势:
1.统一数据的调用方式,因为指针是调用数据的中间层,修改指针即可调用不同的数据,当代码需要在不同情况下调用不同数据时,可以使用指针统一调用,只需要在不同情况下修改指针为不同的内存地址即可,无需编写多组代码分别调用这些数据,当数据需要在多处代码中同时调用、并在不同情况下同时修改时,使用指针的优势更明显,修改了指针就等于修改了所有使用指针调用数据的代码。
2.突破编译器限制,使用指针调用数据时编译器不会进行限制,只要操作系统不禁止即可,可以通过此特性绕过编译器的某些限制,比如在一个函数内调用另一个函数的局部数据。
3.接收未知的内存地址,比如向操作系统申请内存,比如调用动态链接库成员。
指针的长度在不同计算机中是不同的,在32位处理器程序中指针是无符号4字节整数,在64位处理器程序中指针是无符号8字节整数。
依据指向数据的类型可以将指针分为多种,指针用于存储哪种类型数据的地址就属于哪种类型的指针,编译器通过指针类型确定要操作多少个内存单元,int类型指针操作4个内存单元。
#include <stdio.h>
int main()
{
int a = 9;
int * p1 = &a; //int指定指针类型,*符号表示定义指针,p1为指针名,使用&符号提取一个数据的地址进行赋值,注意这里的&符号并非表示与运算
printf("变量a的值为:%d\n", *p1); //使用 *p1 调用指针指向的数据
printf("变量a的地址为:%p\n", p1); //使用 p1 调用指针本身
p1 = 0; //指针不再使用后修改为0,避免错误调用
return 0;
}
使用指针可以随意调用数据,数据调用方式更灵活,灵活的代价是容易出错,使用指针时应该做到代码严谨,同时定义指针暂时不使用时应该将其赋值为0,防止直接使用未赋值的指针,若指针占用的内存原有数据可以当做合规的内存地址使用,将会使用一个未知的内存地址,另外指针不再使用后也应该修改为0。
多重指针
指针也可以存储另一个指针的地址,相当于多层间接寻址。
#include <stdio.h>
int main()
{
int a = 5;
int * p1 = &a; //指针
int ** p2 = &p1; //指针的指针
int *** p3 = &p2; //三重指针,很少使用
printf("p2存储的数据为:%p\n"
"p2指向的数据为:%p\n"
"p2最终指向的数据为:%d\n",
p2, *p2, **p2);
return 0;
}
指针变量、指针常量
1.指针变量,指针本身是变量,赋值后可以修改,可以存储变量、常量的地址。
2.指针常量,指针本身是常量,赋值后不能修改,可以存储变量、常量的地址。
3.变量指针,指向变量的指针,存储变量的地址,可以通过指针修改指向的数据,不能存储常量的地址,但编译器默认只是给一个警告,不会禁止。
4.常量指针,指向常量的指针,存储常量的地址,不能通过指针修改指向的数据,也可以存储变量的地址,此时指针将变量认为是常量,并且不能通过指针修改它,若一个指针即需要指向变量也需要指向常量、同时不需要修改指向的数据,可以将其定义为常量指针。
#include <stdio.h>
int main()
{
int a = 0;
const int b = 9;
int * const p1 = &a; //const在指针名之前,定义指针常量
const int * p2 = &b; //const在类型名之前(或类型名与*符号之间),定义常量指针
const int * const p3 = &b; //组合使用
return 0;
}
指针作为数组元素
#include <stdio.h>
int main()
{
int i1=1, i2=2, i3=3, i4=4, i5=5;
int * p1[5] = {&i1, &i2, &i3, &i4, &i5}; //p1为存储指针的数组,元素为int类型指针
printf("%d\n", *p1[0]); //调用数组第一个元素指向的数据
char * p2[2] = {"阿狸", "喜羊羊"}; //可以使用数组为p2赋值,编译器自动取每个数组的地址作为p2元素的值
printf("%s\n%s\n", p2[0], p2[1]); //输出p2两个元素指向的字符串
return 0;
}
● restrict关键词
定义指针时添加restrict关键词表示代码可以保证指针指向的数据不会通过其他指针或者数据名进行修改,只会通过此指针修改,让编译器放心进行高效优化。
int * restrict p1;
【数组指针】
数组指针表示存储数组地址的指针,数组第一个元素的地址就是数组的地址,可以使用数组首元素的指针表示数组指针。
#include <stdio.h>
int main()
{
int a[5] = {1,2,3,4,5};
int * p1 = &a[0];
for(int i = 0; i < 5; i++)
{
printf("%d\n", *p1);
p1++; //指针加1,指针记录的地址增加数据类型长度,这里的int类型指针会+4,定位到下一个元素
}
return 0;
}
数组指针可以使用下标的方式调用数组元素,原理与使用数组名相同,“指针地址+下标*数组元素长度”得出数组元素地址。
#include <stdio.h>
int main()
{
/* 单层指针 */
int a[5] = {1,2,3,4,5};
int * p1 = &a[0];
/* 遍历数组 */
for(int i = 0; i < 5; i++)
{
printf("%d\n", p1[i]); //将指针当做数组名使用,使用下标的方式调用数组元素
}
return 0;
}
数组指针还有另一种定义方式,它记录了数组长度信息,用于对指针赋值时进行检查,若赋值为长度不同的数组的地址则会发出警告,但默认不会禁止编译,这个长度信息只在编译器中记录,编译后的程序并没有记录此信息。
#include <stdio.h>
int main()
{
int a[5] = {1,2,3,4,5};
int (*p1)[5] = &a; //p1为数组指针的名称,只能赋值为5个int类型元素数组的地址
printf("数组第一个元素的值:%d\n", (*p1)[0]); //调用指针指向的数据
printf("数组第一个元素的地址:%p\n", p1); //调用指针存储的地址
return 0;
}
【结构体指针】
#include <stdio.h>
int main()
{
struct zoo
{
char name[20];
int age;
} ali = {"阿狸", 8};
struct zoo * p1 = &ali; //定义结构体指针,只能赋值为同类型结构体实例的地址
printf("%s:%d岁\n", p1->name, p1->age); //使用->符号调用结构体成员
printf("%s:%d岁\n", (*p1).name, (*p1).age); //另一种使用方式
return 0;
}
共用体指针使用方式与结构体指针相同,不再重复介绍。
结构体成员指针
#include <stdio.h>
int main()
{
struct zoo
{
char name[20];
int age;
} ali = {"阿狸", 8};
char * p1 = &ali.name[0];
int * p2 = &ali.age;
printf("%s:%d岁\n", p1, *p2);
return 0;
}
【指针运算】
指针变量可以进行数学运算,不同类型的指针运算结果不同,运算规则如下:
1.单数据指针,指针+1,指针本身增加数据的长度,定位到下一个同类型数据,比如int类型指针+1等于指针值+4,减法同理。
2.数组指针,指针+1,指针本身增加数组元素的长度,定位到数组下一个元素。
3.结构体指针,指针+1,指针本身增加结构体的长度,注意结构体长度需要额外计入成员地址对齐占用的存储空间。
4.函数指针,不支持数学运算。
#include <stdio.h>
int main()
{
int a[5] = {1,2,3,4,5};
int * p1 = &a[0];
/* 遍历数组 */
for(int i = 0; i < 5; i++)
{
printf("%d\n", *p1); //输出
p1++; //每次循环指针+1,定位到下一个元素
}
return 0;
}
数组元素指针相减
指向同一个数组不同元素的指针之间可以进行减法运算,编译器会转换为计算两个指针指向元素的下标相减。
#include <stdio.h>
int main()
{
int a[10] = {0,1,2,3,4,5,6,7,8,9};
int * p1 = &a[5];
int * p2 = &a[8];
printf("%lu\n", p2 - p1); //p2-p1 = 8-5,输出3
return 0;
}
【指针类型转换】
指针类型可以使用代码转换,编译器使用转换后的类型操作内存地址、进行指针运算,转换语法如下:(类型名*)指针名。
指针类型转换只存在于转换运算式中,不影响指针原来的类型,若需永久转换类型可以使用另一个指针接收转换后的指针。
指针类型可以随意转换,编译器不做限制,若一个未知数据需要通过指针逐个操作其中每个字节,可以将指针转换为char类型,之后即可按字节读写其中的数据。
#include <stdio.h>
int main()
{
int a = 257;
int *p1 = &a;
printf("%d\n", *(char*)p1); //int指针转换为char类型,只调用第一个字节,257转二进制 = 0001 0000 0001,低8位为1,输出1
return 0;
}
结构体指针类型转换
#include <stdio.h>
int main()
{
struct k1
{
char c[4];
//......
} ali = {"ali"};
struct k2
{
int i;
//......
};
struct k1 * p1 = &ali;
struct k2 * p2 = (struct k2 *)p1; //p1转换为k2类型
printf("0x%x\n", p2->i);
return 0;
}
空类型指针
指针类型可以定义为void,表示类型为空,空类型指针可以存储任何类型数据的地址。
空类型指针不能直接使用,需要首先转换为具体的类型再使用,否则编译器无法确定要操作多少内存单元。
当你需要接收一个指针但又不确定它的类型时,可以定义一个空类型指针接收,之后再转换为具体类型使用。
#include <stdio.h>
int main()
{
int a = 9;
int * p1 = &a;
void * p2 = p1; //空类型指针
//printf("%d\n", *p2); //错误,不能使用
printf("%d\n", *(int *)p2); //正确,转换为int类型再使用
return 0;
}

06. C语言指针的更多相关文章
- C语言指针转换为intptr_t类型
1.前言 今天在看代码时,发现将之一个指针赋值给一个intptr_t类型的变量.由于之前没有见过intptr_t这样数据类型,凭感觉认为intptr_t是int类型的指针.感觉很奇怪,为何要将一个指针 ...
- [转]C语言指针学习经验总结浅谈
指针是C语言的难点和重点,但指针也是C语言的灵魂 . 这篇C语言指针学习经验总结主要是我入职以来学习C指针过程中的点滴记录.文档里面就不重复书上说得很清楚的概念性东西,只把一些说得不清楚或理解起来比较 ...
- 不可或缺 Windows Native (7) - C 语言: 指针
[源码下载] 不可或缺 Windows Native (7) - C 语言: 指针 作者:webabcd 介绍不可或缺 Windows Native 之 C 语言 指针 示例cPointer.h #i ...
- C语言指针学习
C语言学过好久了,对于其中的指针却没有非常明确的认识,趁着有机会来好好学习一下,总结一下学过的知识,知识来自C语言指针详解一文 一:指针的概念 指针是一个特殊的变量,里面存储的数值是内存里的一个地址. ...
- (转载)c语言指针学习
前言 近期俄罗斯的陨石.四月的血月.五月北京的飞雪以及天朝各种血腥和混乱,给人一种不详的预感.佛祖说的末法时期,五浊恶世 ,十恶之世,人再无心法约束,道德沦丧,和现在正好吻合.尤其是在天朝,空气,水, ...
- 关于C语言指针的问题
在学习关于C语言指针的时候,发现这样一个问题,代码如下: #include<stdio.h> #include<stdlib.h> #include<string.h&g ...
- C语言指针类型 强制转换
关于C语言指针类型 强制转换 引用一篇文章: C语言中,任何一个变量都必须占有一个地址,而这个地址空间内的0-1代码就是这个变量的值.不同的数据类型占有的空间大小不一,但是他们都必须有个地址,而这个 ...
- C语言指针和数组知识总结(上)
C语言指针和数组知识总结(上) 一.指针的基础 1.C语言中,变量的值能够通过指针来改变,打印指针的语句符号可以是: %08x 2.指针的本质 指针的本质就是变量,那么既然是变量,那么一定会分配地址 ...
- C语言指针操作
欢迎访问我的新博客:http://www.milkcu.com/blog/ 原文地址:http://www.milkcu.com/blog/archives/pointer-manipulation. ...
- C语言指针声明探秘
C语言指针声明探秘
随机推荐
- 探秘Kubernetes:在本地环境中玩转容器技术
在云计算时代,Kubernetes 已成为云原生技术的真正基石.它是应用程序容器的编排动力源,可跨多个集群自动部署.扩展和运行容器.Kubernetes 不仅仅是一个流行词,它还是一种模式转变,是现代 ...
- SpringSecurity认证和授权流程详解
什么是SpringSecurity Spring Security是一个Java框架,用于保护应用程序的安全性.它提供了一套全面的安全解决方案,包括身份验证.授权.防止攻击等功能.Spring Sec ...
- requests进阶
requests进阶 三.requests模块处理cookie相关的请求 1 爬虫中使用cookie 为了能够通过爬虫获取到登录后的页面,或者是解决通过cookie的反扒,需要使用request来处理 ...
- CTFshow pwn53 wp
PWN53 那么先看保护 虽然没有开canary但是本题在ida打开看见他是模拟了canary的效果,不同的是他的是固定的canary,但是一样要爆破 而且发现还有后门函数 在ctfshow函数我们发 ...
- gRPC入门学习之旅(五)
gRPC入门学习之旅(一) gRPC入门学习之旅(二) gRPC入门学习之旅(三) gRPC入门学习之旅(四) 通过之前的文章,我们已经创建了gRPC的服务端应用程序,那么应该如何来使用这个服务端应用 ...
- Avalonia的模板控件(Templated Controls)
在Avalonia的UI框架中,TemplatedControl是一个核心组件,它提供了一种强大的方式来创建可重用且高度可定制的控件. 本文将深入探讨TemplatedControl的概念.其带来的优 ...
- CentOS 利用pam控制ssh用户的登录及SSH安全配置
CentOS 利用pam控制ssh用户的登录 有关pam的使用,请找相关的文档.下面只说两个简单的例子. 首先在/etc/pam.d/sshd加入一句: account required ...
- redis 简单整理——客户端哨兵模式[三十]
前言 简单介绍一下客户端的哨兵模式连接. 正文 Sentinel节点集合具备了监控.通知.自动故障转移.配置提供者若干 功能,也就是说实际上最了解主节点信息的就是Sentinel节点集合, 而各个 主 ...
- DRF自动生成接口文档
自动接口文档能生成的是继承自APIView及其子类的视图. 1. 安装依赖 # 生成接口文档需要coreapi库的支持 pip install coreapi 2 设置接口文档访问路径 # 在总路由中 ...
- var ,let和const三者之间的区别
var 声明变量可以被修改,可以被重复声明.有变量提升(写的位置和使用时候的位置不一样). let声明变量可以被修改,,但不能重复声明.如文件中有一个let a = 0;后面就不能在定义let a=* ...