c语言---指针结构体篇
【前言】本系列(初阶)适用于初学者课前预习或者课后复习资料,包含了大部分基础知识点梳理与代码例方便看官理解,有问题请指出。本人邮箱地址:p772307283@outlook.com
可爱捏
目录
1.指针
1.1引入
1.2指针与指针类型
1.3二级指针
1.4指针数组
2.结构体
2.1结构体是什么以及如何声明,定义,初始化
2.2访问与传参
1.指针
1.1引入
我们口头上所指的指针一般来讲指的是指针变量。指针变量是一个专门用来存放地址的变量。而真正的指针代表着地址,是内存中最小的单元的编号。
那什么叫内存呢?
精炼的讲,电脑在运行程序时会将其加载到内存中,在此期间会对内存空间进行一个调用。一个个的内存单元是内存最小组成单位,每一个内存单元都有独特的一串数字为其进行标识。我们称为地址。并且我们可以通过&(取地址操作符)取出变量的内存实际地址,其实就是第一个字节的地址。例如int占4个字节,那么&会取出其第一个字节的地址。
来看一个简单的指针的例子。
int main()
{int a = 10;int* p = &a;//为a开辟了一块空间存储了10,使用了取址操作符取出了a的第一个字节地址,并存到了p中return 0;
}
这就是一个简单的指针的运用。
那指针变量的大小是多少呢?是否和前面的数据类型有关呢?往下看。
对于32位机器而言,有32位地址线,那么通过产生高低电压的情况,会在每位上生成1或者0,那么就会有2^32种情况了。相应的,就有2^32个地址了。
每个地址占一个字节,那2^32个地址大概就是4gb的空间。同时,因为一个地址是32位,所以是4btye,也就是4个字节。
相应的,如果是64位机器,则为8个字节。
综上,指针的大小在32位平台上是4个字节,但是在64位平台上就是8个字节了。
接下来进入正题。
1.2指针与指针类型
变量都有各种类型来进行区分,那么指针呢?肯定也是存在的。
但是正如上面讲的,指针的大小被地址线所限制,也就是和类型并没有关系。
那为何要指针规定类型呢?他仅仅的只是为了存储相同类型的变量的地址吗?
让我们通过几个示例来了解指针类型的作用。
指针加减整数:
int main()
{int n = 1;int* pi = &n;char* pc = (char*)&n;//必须要强制类型转换,否则会有警告或报错 printf("%p\n", &n);printf("%p\n", pi);printf("%p\n", pi+1);printf("%p\n", pc);printf("%p\n", pc+1);return 0;}
结果
000000492372F7C4
000000492372F7C4
000000492372F7C8
000000492372F7C4
000000492372F7C5
可以看到的是1,2,4都得到了相同的地址。说明pi和pc作为指针发挥的作用是正常的。但是pc+1和pi+1却有所区别了。pi+1从c4变到了c8,pc+1却从c4变成了c5
说明了什么问题?指针的类型决定了指针向前或向后走一步的距离有多大。
还有其他的用处吗?
解引用操作:
在指针前加上一个*就是解引用操作,作用是修改*pi相当于修改n。此时的*pi就等于n
int main()
{int n = 0x12345678;int* pi = &n;char* pc = (char*)&n;*pc = 0;*pi = 0;return 0;
}
通过监视窗口来看内存的变化。
初始的时候
当经过了*pc
可以发现78变成了00
再经过*pi
全部变成了0
这是怎么一回事呢?
指针的类型决定了对指针解引用时可以有多大的权限,很明显char型指针就只有访问一个字节的权限,所以只把78改成了00,而int型则具备访问4字节的权利。
综上,指针的不同类型其实就是提供了不同的视角来观看访问内存时的情况。
再扩展讲讲野指针。
什么叫野指针呢?指针的指向位置是随机的或者不正确的,没有明确的限制的,诸如此类我们就把他称作野指针。
什么情况会出现野指针呢?
指针未初始化
int* p;
指针越界访问
int main()
{int arr[5] = { 1,2,3,4,5 };int* p = arr;for (int i = 0; i < 6; i++){*(p++) = i;}
}
指针越界了,i在此循环了6次。
指针指向的空间被释放
int* test()
{int a = 10;return &a;
}
int main()
{int* p = test();printf("%d", *p);
}
运行会成功,这是因为test所创建的栈帧暂时还没有被销毁。
如果在printf前加上一句其他的,结果就变了。
int* test()
{int a = 10;return &a;
}
int main()
{int* p = test();printf("you're wrong!\n");printf("%d", *p);
}
结果为
you're wrong!
-858993460
这是因为函数在调用时会创建栈帧,当函数调用完,为了节省空间就会对栈帧进行销毁。当我调用另一个printf时,原先的test就会被销毁,那么指针就找不到地址了,也就成了一个野指针。
具体的博客可以参考
(2条消息) 从底层深度分析栈帧(c语言)_丞子_516的博客-CSDN博客
所以对于野指针一定要小心又小心。
指针与指针也可以相减。但前提是两个指针都同时指向同一片空间才行。
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("%d", &arr[9] - &arr[0]);
}
得到的结果是9,由此可见,其相减得到的结果是指针之间的元素个数。
但注意,如果是两数掉过来相减,得到的就是-9
这个性质还可以用于计算字符串的长度。
int main()
{char s[] = "abcdef";char* p1 = s;char* p2 = s;while (*p2 != '\0'){p2++;}printf("%d", p2 - p1);
}
需要注意的是,因为s已经代表地址,所以不再需要&,另外,p1默认指向s的首元素地址。
再看一例代码
for (p = &arr[5]; p >= &arr[0]; p--)
{*p = 0;
}
将arr的五个元素都赋予了0
但需要注意的是
标准规定,允许指向数组元素的指针与指向数组的最后一个元素后面的那个内存位置的指针相比较,但不允许与指向第一个元素前的那个内存位置的指针进行比较。
所以虽然大多数编译器以完成,但你需要明白这样的写法是有错误的。
同时这样并不存在数组越界的情况。因为 指针并未调用越界的下标,只是取到了他的地址而已。这样是不会报错的。
1.3二级指针
变量都是有给分配地址的,那么我们上文提到过,指针也是一个变量,那么指针也是有地址的,其地址就存放在二级指针里。
int a = 10;
int* pa = &a;
int** ppa = &pa;
如上面的代码,为a这块空间放进了10,再将a的地址存放到pa中,将pa的地址存放到ppa中。
从指针的类型就可以看出,a是int型的,pa是int*,*代表他是一个指针,int*则代表着这个指针指向int,而ppa是int**,同样的,第二个*代表着ppa是指针,而int*则代表此指针是指向int*的。
二级指针有什么运算呢?
*ppa,也就是解引用操作,可以找到pa,再使用*pa,即得到了a
上面的操作就相当于**ppa
1.4指针数组
指针数组到底是指针还是数组呢?
先来理清一下指针和数组之间的关系。
首先要清楚指针和数组并非是相同的类型。
指针是一个变量,用来存放地址。
而数组则是用来存放一组相同类型的元素。
同时,数组名就是数组首元素的地址。
所以我们可以用指针来访问数组。
并且比较灵活的一点,只要知道了首元素的地址,就可以通过下标进行访问。
那么就意味着p[1]可以去访问数组的arr[1].
int arr[]={1,2,3,4,5};
int *p=arr;
而指针数组的本质上仍然是一个数组。而且同整型数组,字符型数组并无太大的区别。
指针数组里面的存放的就是一组相同类型的指针。
例如,指针数组可以以1维数组的形式去模拟2维数组。
int main()
{int a[] = { 1,2,3,4 };int b[] = { 5,6,7,8 };int c[] = { 9,10,11,12 };int* arr[3] = { a,b,c };for (int i = 0; i < 3; i++){for (int j = 0; j < 4; j++){printf("%d ", arr[i][j]);}}
}
这里的arr[i]就相当于a,b,c,那么arr[i][j]就等于a[j],b[j],c[j]
2.结构体
2.1结构体是什么以及如何声明,定义,初始化
结构是什么呢?结构就是一些不相同类型的值的集合。
这些值被称作成员变量。
就像我们想去描述一个人的特征,可以从名字,年龄,性别,工作入手,将这些成员变量放到结构体中,就完成了声明。
struct person
{char name[20];char sex[10];int age;char position[20];
};
当然,这些成员变量可以是各种类型,比如还可以是指针,其他结构体。
如何初始化?
struct person p1;
这样就初始化了一个名为p1的结构体。
也可以进行赋值。
struct person p1 = { "taylor","female",18,"singer" };
结构体的嵌套
struct score{int math;int chinese;};struct stu{int age;char name[20];struct score s;};struct stu p = { 12,"taylor",{78,89} };
注意,我们一般将结构体放在main函数的外面,因为有时候函数调用时也会用到结构体,倒不如直接放到main的外面,让其可以被反复利用。
2.2访问与传参
结构变量成员是通过点操作符来进行访问的。
struct stu q;
q.age = 12;
strcpy(q.name, "taylor");
这样我们就将q的age改为了12,名字改为了taylor
结构体指针也可以访问变量的成员。
如:
struct stu {int age;char name[20];
};
void print(struct stu* s)
{printf("name:%s,age:%d", (*s).name, (*s).age);
}
int main()
{struct stu s = { 30,"taylorswift"};print(&s);
}
也有简化操作。
(*s).name
改为
s->name
在这里也可以不用借用传指针的方式传结构体,也可以将整个结构体传进去。
struct stu {int age;char name[20];
};
void print(struct stu s)
{printf("name:%s,age:%d", s.name, s.age);
}
int main()
{struct stu s = { 30,"taylorswift"};print(s);
}
但仍然只推荐使用指针进行传参。
因为函数在传参时,需要进行压栈操作,若是传递一整个结构体进去,那么压栈的开销过大,性能一定会下降。
感兴趣的话可以看看关于栈帧的理解。里面详细解释了函数是如何进行传参的。
栈帧的底层分析
c语言---指针结构体篇相关推荐
- c语言指针结构体心得,C语言结构体指针陷阱
一."."和"->"区别: 当声明一个结构体时,因为自动分配了结构体的内存空间,所以可以直接用"."访问相应结构体内的存储单元,比如: ...
- [ C语言 ]指针---结构体(struct)---分支语句(1)---初识C语言末尾
文章目录 前言 一,指针 二,结构体(struct) 三,分支语句(选择结构) 总结 前言 本篇文章主要记录指针末尾,结构体,和分支语句(选择结构) --------------------- ...
- C语言指针结构体详解,结构体指针,C语言结构体指针详解
结构体指针,可细分为指向结构体变量的指针和指向结构体数组的指针. 指向结构体变量的指针 前面我们通过"结构体变量名.成员名"的方式引用结构体变量中的成员,除了这种方法之外还可以使用 ...
- c语言指针变量输出不了共用体,瘋子C语言札记(结构体/共用体/枚举篇)
瘋子C语言笔记(结构体/共用体/枚举篇) (一)结构体类型 1.简介: 例: struct date { int month; int day; int year; }; struct student ...
- c语言中结构体头文件是什么,函数形参里有结构体指针,为什么在头文件生
满意答案 lam950 2017.04.08 采纳率:59% 等级:8 已帮助:913人 C语言用结构体指针作函数参数 这种方式比用结构体变量作函数参数效率高,因为无需传递各个成员的值,只需传递 ...
- c语言如何将值赋给结构体指针,C语言给结构体指针赋值
<C语言给结构体指针赋值>由会员分享,可在线阅读,更多相关<C语言给结构体指针赋值(6页珍藏版)>请在人人文库网上搜索. 1.指向结构体的指针 在C语言中几乎可以创建指向任何类 ...
- 更易上手的C语言入门级芝士 (3) 常见关键字+define+指针+结构体(超详细)
目录 1.常见关键字 1.auto,break 2.switch,case,default,break 3.char,int等类型关键字 4.const 5.continue 6.extern ...
- 【C 语言】结构体 ( 结构体 数组 作为函数参数 | 数组 在 堆内存创建 )
文章目录 一.结构体 数组 作为函数参数 ( 数组 在 堆内存创建 ) 二.完整代码示例 一.结构体 数组 作为函数参数 ( 数组 在 堆内存创建 ) 在上一篇博客 [C 语言]结构体 ( 结构体 数 ...
- C语言之结构体和共用体
C语言之结构体和共用体 算上这篇笔记加上之前的四篇笔记,C语言基础我们也就告一段落了,对于刚刚接触c语言的童鞋们来说,这些以及足够了,稍后我会发布数据结构,对于想要深入学习的童鞋可以继续关注.本人也算 ...
最新文章
- Mongodb查询引用
- Android Activity的启动模式及对生命周期的影响
- Boost:bimap双图property地图的测试程序
- Jetty和Tomcat的比较
- liferay form 小案例
- YV12 and NV12异同,
- CVPR 2022 | 即插即用!南洋理工商汤开源SAM-DETR: 利用语义对齐匹配实现快速收敛的DETR...
- Android 操作权限大全 (android.permission)
- jsp内置对象销毁session
- Jmeter之连接MySQL数据库操作
- jms和activemq
- 06-Docker数据管理实践
- 修改centos系统时间
- API接口设计的五大公共参数
- 谷歌地球二次开发基础平台
- 量化私募FOF为何“脱颖而出”?
- 第五次网页前端培训笔记(javascript基础内容)
- 【2000字原创】大厂都在用的几款软件测试平台!
- powerbuilder的dw中使用graph风格,当横轴是日期时,如何显示才能完整显示日期?
- 美国拟发布纽扣电池或硬币电池安全标准和通知要求ANSI C18. 3M