指针和结构体(初级)
目录
- 前言
- 指针是什么
- 总结:指针就是变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)
- 指针和指针类型
- 指针的关系比较
- 指针-指针
- 指针和数组的关系
- 二级指针
- 指针数组
- 野指针
- 野指针的原因
- 避免野指针的方法
- 结构体
- 结构的基础知识
- 结构体的声明和定义
- 创建结构体的偷懒式写法(简洁版)
- 结构体变量定义初始化
- 结构体嵌套
- 结构体变量传参
- 1、值传递
- 2、地址传递
- 总结:地址传递效率更高
前言
作为一名C程序员,学会指针是我们的首要关键,灵活运用指针又是对个人能力的体现,想从此刻学好指针请跟着博主来
指针是什么
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向
(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以
说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元
总结:指针就是变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)
每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB ==
232/1024/1024MB==232/1024/1024/1024GB == 4GB) 4G的空闲进行编址。
同样的方法,那64位机器,如果给64根地址线,那能编址多大空间,自己计算。
这里我们就明白:
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所
以一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地
址。
总结:
1、指针是用来存放地址的,地址是唯一标示一块地址空间的。
2、指针的大小在32位平台是4个字节,在64位平台是8个字节
指针和指针类型
总结:指针的类型决定了指针向前或者向后走一步有多大(距离)。
指针作用域
int main()
{int n = 0x11223344;char *pc = (char*)&n;int *pi = &n;*pc = 0;pi = 0;return 0;
}
总结: 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。 比如: char* 的
在我们的调试窗口中可以看到,此时的pi,pc指针都指向着变量n,程序进一步往下走
注意:断点走到哪一行表示这一行还没执行,断点的上一行刚刚执行,此时的断点走到了11行,表示第10行已经执行,这个过程我们记录了*pc只改变了变量n在内存中的第一个字节,
而指针*pi却能改变四个字节
总结:char指针解引用就只能访问一个字节,而 int 的指针的解引用就能访问四个字节。
指针的关系比较
以下代码的作用是遍历修改数组中的每一个元素
#define N_VALUES 5float values[N_VALUES];float *vp;for (vp = &values[0]; vp < &values[N_VALUES];){*vp++ = 0;
}
眼尖的小伙伴可能会觉得这样一弄不是就越界了吗?
在这里可以注意的是下标为5的数组地址可以用来必较VP地址间的关系,这是因为标准规定
注意这里的 vp++ 不得不说前置++与后置++跟指针的关系是紧密结合的,务必把这里的优先级搞清楚, 因为++的优先级要高于(解引用), 但又因为这是后置++(先用后加),所以会先计算 *(vp++),对vp解引用找到vp地址将vp地址对应的内容修改为0,之后指针再++,(注意这里是float型指针,占四个字节,指针步长为4,请务必结合前面内容加以理解)
vp < &values[N_VALUES],指针的关系运算,两个指针之间比较大小
指针-指针
指针 - 指针 得到的数字的绝对值是指针和指针之间元素的个数,这句话是什么意思?
读代码理解
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,0};printf("%d\n",&arr[9] - &arr[0]);//9printf("%d\n",&arr[0] - &arr[9]);//-9return 0;
}
这句话的意思其实无非就是说,在一段连续的空间里(我这里是数组),有了地址头(&arr[0]),又有了地址尾(&arr[9]),这两个指针一减,得到的结果值其实就是我们的元素个数
模拟strlen函数(指针减指针)
以下是通过模拟strlen函数的实现,也是通过两地址相减,取得字符间的个数
int my_strlen(char *str)
{char* start = str;//指针备份if(*str != '\0'){str++; }return str - start;//两指针相减得到字符个数
}
指针和数组的关系
数组名是数组首地址,但是有两个例外
1、sizeof(数组名) - 数组名不是首元素地址,是表示整个数组,这里计算的是整个数组的大小,单位是字节
2、&数组名 - 这里的数组名不是首元素的地址,是表示整个数组的,拿到的是这个数组的地址 -
额外补充(图解):
以上观察貌似区别不大,似乎也不足以证明&arr就是整个数组的地址,看下一步
我们直到arr 跟 &arr[0]都是数组首地址(数组首元素地址),当它们 + 1后是跳过一个整形的地址,而&arr表示的是整个数组的地址,整个数组的地址不就是40字节吗?在窗口中可以看到光标选中的那一行,跟它的下一行之间是相差了40个字节的,因为16进制的28 就是十进制的40,这一点足以证明&arr取出的是整个数组的地址
既然可以把数组名当成地址存放到一个指针中,那么我们就可以使用指针来访问一个数组
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,0};int len = sizeof(arr) / sizeof(arr[0]);int i = 0;for(i = 0; i < len; i++){printf("%d",*(p + i)); }return 0;
}
二级指针
int main()
{int a = 10;int *p = a; //指针变量存放a的地址int **pp = &p;//二级指针存放一级指针的地址int ***ppp = &pp;//三级指针存放二级指针的地址return 0;
}
上述代码可以这么理解和记忆int *p = a ==》 *p表示的是一个指针变量,指针变量指向的对象类型是int类型int* *pp = &p ==》 *p表示的是一个指针变量,指针变量指向的对象类型是int*类型int** *ppp = &pp ==》*p表示的是一个指针变量,指针变量指向的对象类型是int**类型
以下主要讲解到二级指针的逻辑,因为二级指针已经是我们理解和使用的极限了,代码书写的时候并不是写的越复杂,才越是高质量,恰恰是以最能让别人能够看得懂才是优质的代码,因为这样大大便于软件的维护和测试
这是以上代码得到的测试结果,为了便于大家理解,简单的打个比方
1、假设我们现在有3个抽屉和2把钥匙
2、抽屉1存放的是局部变量a
3、抽屉2存放的是*p
4、抽屉3存放的是**pp
逻辑梳理:首先我们如果想改变局部变量a的值是可以的并且有两种方法,
方法一:直接修改变量a的值
方法二:通过一级指针*p拿到变量a的地址,从而修改变量a
方法三:通过二级指针 *pp 一解引用拿到了一级指针,再通过对一级指针解引用就会找到局部变量a,从而修改变量a的值
主要讲解二级指针:如果我们把二级指针看成一个抽屉的话,那么打开抽屉的第一时间就会找到一把钥匙,这把钥匙就是&p
钥匙对应打开的箱子是序号2吧,当我们打开了第二个箱子的话,此时2号箱子里存放的就是&a钥匙了,那有了这一把钥匙于是乎就可以再去打开1号箱子了,1号箱子里存放的是变量a,那么是不是就可以直接操作变量a了?答案是可以
指针数组
如何理解指针数组呢?
首先指针数组的本质上是一个数组,指针数组的属性是每一个元素都是一个指针变量,既然他是一个数组的话想必一定是一块连续存储的空间了,每一个元素都是一个指针变量?那么是不是每一个元素都可以用来存放一个变量的地址,指向这个变量?
#include<stdio.h>
int main()
{int a = 0;int b = 2;int c = 4;int *arr[3] = {&a,&b,&c};int len = sizeof(arr) / sizeof(arr[0]);int i = 0;for (i = 0; i < len; i++){printf("%d ",*(arr[i]));}return 0;
}
注意不要写成 int len = sizeof(arr) / sizeof(*(arr[0]));这种形式,数组类型是整形数组时必然不会存在什么问题但是如果是char 类型呢,
在我们的调试窗口下可以很清晰的看得到len的值居然是12,所以不要写成这种方式的,原因也很简单,因为指针在32位系统下是默认占4字节,我们测试的环境就是在
32系统下,那么sizeof(arr)计算的是整个数组的大小(字节),而 sizeof((arr[0]))他是取出指针变量指向的那个地址上对应的值,由于这里涉及到隐士类型转换,所以最终sizeof((arr[0]))求得的是1个字节,12 / 1 = 12,答案不就出来了吗,这里交代的是一个细节问题
回归到正题想必大家应该知道这段代码的功能吧,没错就是依次取出数组中的指针,再对指针解引用,找到指针指向的那个变量逐个打印出来
看到这里大家应该已经明白了,指针数组是一个数组,由于指针数组的每一个元素都是一个指针变量,指针变量具有指向性
野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针的原因
1、指针未初始化
2、指针越界访问
3、指针指向的空间释放
避免野指针的方法
1、指针初始化
2、小心指着越界
3、指针指向空间释放即使置NULL
4、指针使用之前检查有效性
结构体
结构的基础知识
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量
结构体的声明和定义
struct Stu //结构体的声明,创建一个结构体类型
{char name[20];int age;char id[20];
}; struct Stu //创建一个结构体类型
{char name[20];char author[15];float price;
}b1,b2; //创建两个全局的结构体变量struct Point //创建一个结构体类型
{int x;int y;
}p1,p2; //创建两个个全局的结构体变量 int main()
{struct Stu b1;//创建一个局部的结构体变量struct Stu b2;//创建一个局部的结构体变量return 0;
}
以上两种创建结构体变量的方式都是允许的,只不过再main中创建的结构体变量是存放在栈上的,而b1、b2、p1、p2是全局的结构体变量,全局变量是存放在静态区的,注意创建一个结构体类型时分号不能丢
创建结构体的偷懒式写法(简洁版)
例如描述一个动物
typedef struct Animal //创建一个结构体类型
{char families[20] // 描述动物科char skill[20] //动物技能char breed[20] //动物的繁殖方式}Animal; int main()
{struct Animal tiger //创建一个老虎对象Animal mew //创建一个海鸥对象return 0;
}
在主函数中使用以上两种定义结构体变量的方式都可以,这里值得一提的是Animal是一个类型使用typedef给整个结构体类型取了一个别名叫Animal,简单说明以下typedef关键字,这个关键字是给类型取别名的,比如int类型是一个整形,我也可以使用typedef给int类型取别名为intjj,此时的
intjj 表示的还是一个整形,未来在使用的时候intjj类型定义一个变量,这个变量就是一个整形
值得一提的是使用typedef定义出的新类型,只是一个类型而并不是一个变量,跟之前的在末尾定义结构体变量的意义是截然不同的,一种是声明结构体类型的时候定义结构体变量,而第二种使用typedef只是给原先的结构体类型取别名
结构体变量定义初始化
为了便于大家理解还是采用之前的代码
typedef struct Animal //创建一个结构体类型
{char families[20] // 描述动物科char skill[20] //动物技能char breed[20] //动物的繁殖能力}Animal; //结构体类型别名,表示的是同一种类型int main()
{struct Animal tiger = {"猫科","捕猎","***"};Animal mew = {"欧科","飞翔","**"};return 0;
}
是不是非常简单的初始化方式?没错
结构体嵌套
typedef struct birthday //生日
{int year;//年int month;//月int day;//日}birthday;typedef struct Student //学生
{int age;//年龄char name[20];//姓名birthday b1;char occupation[20];//职业}Student;
int main()
{Student s1 = { 18,"张三",{2002,4,8},"经理", };//结构体嵌套初始化printf("%d %s %d %d %d %s",s1.age,s1.name,s1.b1.year,s1.b1.month,s1.b1.day,s1.occupation);return 0;
}
使用操作符访问结构体成员
typedef struct birthday
{int year;int month;int day;}birthday;typedef struct Student
{int age;char name[20];birthday b1;char occupation[20];}Student;
int main()
{Student s1 = { 18,"张三",{2002,4,8},"经理", };printf("%d %s %d %d %d %s\n",s1.age,s1.name,s1.b1.year,s1.b1.month,s1.b1.day,s1.occupation);Student *p = &s1;printf("%d %s %d %d %d %s\n", p->age,p->name,p->b1.year,p->b1.month,p->b1.day,p->occupation);printf("%d %s %d %d %d %s\n", (*p).age, (*p).name, (*p).b1.year, (*p).b1.month, (*p).b1.day, (*p).occupation);return 0;
}
结构体变量传参
1、值传递
2、地址传递
首先先看值传递
void print(Student s1)
{printf("%d %s %d %d %d %s\n", s1.age, s1.name, s1.b1.year, s1.b1.month, s1.b1.day, s1.occupation);
}
int main()
{Student s1 = { 18,"张三",{2002,4,8},"经理", };print(s1);return 0;}
参数传递以值传递的方式,就是一次数据的拷贝(每次都会拷贝sizeof(结构体变量)),将实参s1拷贝到形参s1,值拷贝是会额外地分配一块空间的(sizeof(结构体变量)),结构体变量的大小(字节)一般来讲都会比较大一些,如果每次都是以值拷贝的方式将实参的成员变量挨个拷贝至形参的成员变量,这对于运行效率和空间占用来讲是会大大损耗的(不推荐),
解决方案:地址传递
地址传递
void print(Student *p)
{printf("%d %s %d %d %d %s\n", p->age, p->name, p->b1.year, p->b1.month, p->b1.day, p->occupation);
}
int main()
{Student s1 = { 18,"张三",{2002,4,8},"经理", };print(&s1);return 0;
}
但是如果是地址传递的话,那就不一样了,因为指针的默认字节数是看操作系统的,那如果是在32位平台下的话,指针默认4字节,每次都只需要传递一个地址过去,只占四字节,极大地提升了效率,减少了空间的浪费
总结:地址传递效率更高
指针和结构体(初级)相关推荐
- c语言指针和结构体难点,C语言指针和结构体
第一章 指针 知识点1. a. 函数内的局部变量存在了STACK,全球变量存在了GLOBALS. b. 指针和变量一样,内容是地址 c. *variable = address d. &add ...
- 【黑马程序员 C++教程从0到1入门编程】【笔记1】数据类型、运算符、程序流程结构、数组、函数、指针、结构体
黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难 文章目录 1.C++初识 1.1 第一个c++程序 1.2 注释 1.3 变量 1.4 常量 1.5 关键字 1.6 标识符命名规则 2 ...
- C++阶段01笔记汇总【C++软件安装、C++初识、数据类型、运算符、程序流程结构、数组、函数、指针、结构体】
C++| 匠心之作 从0到1入门学编程[视频+课件+笔记+源码] 目录 C++课程安排 1 C++初识 1.1 第一个C++程序 1.1.1 创建项目 1.1.2 创建文件 1.1.3 编写代码 1. ...
- new 结构体指针_Go:我应该用指针替代结构体的副本吗?
logo 对于许多 golang 开发者来说,考虑到性能,最佳实践是系统地使用指针而非结构体副本. 我们将回顾两个用例,来理解使用指针而非结构体副本的影响. 1. 数据分配密集型 让我们举一个简单的例 ...
- Golang——指针的使用、数组指针和指针数组、指针与切片、指针与结构体、多级指针
指针: 指针是一个特殊的变量,因为存储的数据不仅仅是一个普通的数据,而是一个变量的内存地址,指针本身也是有内存地址的 指针的数据类型有int.float.bool.string.数组.结构体 指针的作 ...
- C语言 指针与结构体
结构体指针 # include <stdio.h>//引用函数库 # include <stdlib.h> struct info{char name[4];int age; ...
- C语言基础1:初识C语言(转义、注释;数组、操作符、反码、补码、static、define、指针、结构体常变量;局部变量;作用域、生命周期)
文章目录 C语言基础1:初识C语言 1.C语言简介 1.1什么是C语言 1.2C语言的发展 2.第一个C语言程序 2.1创建项目 2.2添加源文件 2.3写代码 3.数据类型 4.变量.常量 4.1定 ...
- C语言实现简单的图书馆小程序(练习指针和结构体的基本使用)
放弃不难,但坚持一定很酷. 目的:练习指针和结构体的基本使用. 题目:构建一个"图书馆"小程序(library),让用户将书籍的基本信息都录入到里面,最后一并打印出录入 ...
- C++结构体(结构体创建,结构体数组,结构体指针,结构体嵌套结构体,结构体做函数参数,const变量使用)
C++结构体(结构体创建,结构体数组,结构体指针,结构体嵌套结构体,结构体做函数参数,const变量使用) 目录 C++结构体(结构体创建,结构体数组,结构体指针,结构体嵌套结构体,结构体做函数参数, ...
最新文章
- MySQL基础篇:逻辑架构
- MongoDB C++ gridfs worked example
- Linuxnbsp;JDK1.4卸载与1.6的安装
- 信息系统项目管理师优秀论文:项目风险管理
- 【Linux系统编程】进程间通信之消息队列
- javascript高程3 学习笔记(二)
- centos arm-linux-gcc,CentOS 6.4配置arm-linux-gcc交叉环境
- 【C++学习笔记二】C++继承
- 小米路由器dns辅服务器未响应,小米路由器频繁掉线的原因与解决办法
- Python爬虫_数据存储
- 农历php,php阳历转农历优化版
- python脚本批量登录crt_python批量修改SecureCRT会话密码-阿里云开发者社区
- 动态规划(一)——矩阵连乘
- win7设置环境变量未生效
- Result的类型分析和总结
- 上海公积金提取办法(外地购房,公积金在上海)
- ElasticSearch的REST APIs 之 索引的别名(alias)管理
- 为何现在的90后员工的离职率越来越高了?
- How Broswer Work
- 近似概率编程文献综述
热门文章
- 【问题】vcenter7升级遇到“Exception in invoking authentication handler unidentifiable C++ exception”
- Win10安装程序报错2503和2502错误解决方案
- linux删除指定的行
- java class的方法_Java Class类常用方法描述
- k860i 4.2root成功,用root大师20130705
- python爬虫精进参考答案_高校邦数据科学创新通识课【Python爬虫】课后习题答案...
- css动画其他div,删除另一个div时的CSS3 Transition动画
- 基于 NCC/灰度信息 的模板匹配算法(QT + Opencv + C++),10ms内获取匹配结果,部分源码
- 计算机应用基础是背的吗,计算机应用基础Excel2003电子表格系统
- 【100%通过率】华为OD机试真题 C++ 实现【简单的自动曝光】【2022.11 Q4 新题】