C语言进阶知识板块总结
文章目录
四大板块
首先我把c语言进阶的内容分成了四个部分
- 数据类型
- 函数
- 指针
- 位域(位段)
事先声明:博主也是正在进步的小菜鸟一枚
只是之前在学习的过程中遇到很多地方的知识点理解不够,后面才发现
希望帮助更多的兄弟好好学习,也能顺便总结自己的知识,毕竟分享知识也是在进步,第一次写的东西可能不太好,欢迎大家提出意见,我们一起加油!
一、数据类型的分类
1.基本数据类型
- 整型 :short、int、long、longlong
- 字符型:char
- 浮点型:float、double
2.数组
1)一维数组
定义:数组就是一种相同类型元素的集合
(切记一定是相同的元素,如果是不同的元素结合就是结构体了)
一维数组的定义方式:基本类型+数组名+[元素个数/也可以不写具体元素个数]
//1.一维数组的定义方式:基本类型+数组名+[元素个数/也可以不写具体元素个数]
char arr[];//没有具体个数的字符型元素的数组
char arr1[10];//10个元素为字符型的数组
int arr2[10];//10个元素都为整型的数组
//2.一维数组的初始化方式
// 完全初始化--即给每个元素都初始化
char arr1[10] = "";//每个元素都初始化成0
int arr2[10]={0,1,2,3,4,5,6,7,8,9,};//
// 部分初始化
char arr1[10] = "abcd";//这里我只给前4个元素赋值了
int arr2[10] = {0,1,2,3};//同上,后面没有赋值的元素一般默认为0
2)二维数组和多维数组
定义:和一维数组一样都是相同元素的集合,但是它也可以理解成是多组相同的一维数组的集合
多维数组的定义方式:元素的基本类型+数组名+[一共有多少组一维数组/可以不写][每个一维数组的元素个数/必写]
//多维数组的定义方式:元素的基本类型+数组名+[一共有多少组一维数组/可以不写][每个一维数组的元素个数/必写]
char arr[][5];//arr拥有无具体个数的一维数组,每个一维数组中都有5个元素,每个元素都是char,所以arr的类型为 char [5]
int arr1[3][5];//arr1拥有3组一维数组,其中每组一维数组拥有5个元素,每个元素的类型为int,所以arr1的类型为 int [5]
//二维数组的初始化方式
//完全初始化
char arr[2][5]={"abcde","fghij"};
int arr1[3][5]={{0,1,2},{3,4,5},{6,7,8}}
//部分初始化
char arr[2][5]={"abc","efg"};//这里我初始化了前两个元素
int arr1[3][5]={{0,1},{3,4}};//同上
需要注意的是:
一维数组的每个元素都是一个基本类型,而二维数组中的每个元素都是一个一维数组,千万不能混淆了
//arr中的每个元素类型都是int,第一个元素是0,第二个是1
int arr[5]={0,1,2,3,4};
//arr1中的每个元素类型都是 int [2],所以第一个元素是一维数组{0,1},第二个元素是{2,3}
int arr[3][2]={{0,1},{2,3},{4,5}};
3)变长数组
定义:顾名思义,可以变长的数组,就是在定义的时候元素个数是一个变量,根据使用者的需要可以通过改变变量的大小控制数组的长度
但是要记住变长数组是不能初始化的!!!
且变长数组中存放数据的个数不能超过定义变量的大小
//首先定义一个变量作为数组开始的元素的个数
int size = 10;
//此时这个数组的长度为10
int arr[size];//变长数组不能初始化,只能后续赋值
//如果在使用中觉得arr太长或太短,通过改变size的大小来自定义
size = 20;
arr[size];//此时arr的大小为20
size = 5;
arr[size]//此时arr的大小为5
三、自定义类型
大多数时候,我们需要面向的对象往往不只数字,字符那么简单,这个时候就需要一种数据类型能够对复杂的对象进行描述,这种数据类型就是结构体
(PS:结构体的掌握非常重要,不管是考试还是工作运用的地方非常多,大家一定要好好掌握)
1.结构体
1)基本概念
定义:结构体是不同数据类型的集合,结构体中每个数据类型都叫做成员变量
定义结构体类型的方法:
例如:我要描述一本书,这本书有书名,价格,版号,等等这时候就能用结构体来定义
struct book
{
//书名
char name[10];
//书的价格
int price;
//书的版号
char number[20];
};
//这样一本书的类型就简单的定义好了
结构体的定义方法有3种
1.在类型后面定义
struct book
{
//书名
char name[10];
//书的价格
int price;
//书的版号
char number[20];
}book1,book2;//这里定义了两个元素book1和book2
2.直接定义
在定义好结构体类型后直接定义
struct book
{
//书名
char name[10];
//书的价格
int price;
//书的版号
char number[20];
};
struct book book1;
struct book book2;
//这样我利用定义好的结构体类型book定义了两个元素book1和book2
3.匿名定义
就是在类型定义的时候不写结构体标签,就是匿名定义
但是这种方法只能定义一个结构体变量,不能定义多个!
struct //没有book标签
{
//书名
char name[10];
//书的价格
int price;
//书的版号
char number[20];
}book1;//只能定义一个变量book1,因为是匿名的类型
//如果定义了两个变量book1和book2,系统并不清楚它们是否为同一种类型,这种方法是错的
2)结构体的初始化方法
结构体初始化的方法有3种:
1.完全初始化
struct book book1 = {"今天你学习了吗",10,"2022-5-28-6666"};
//book1的书名是<<今天你学习了吗>>
//价格为10
//版号为2022-5-28-6666
//这里我对每个成员都初始化了
2.部分初始化
struct book book1 ={"今天你学习了吗",10};
//这里我只初始化了书名和价格
3.指定成员初始化
想要访问指定的结构体成员时我们需要用"."这个符号来访问
如果是指针的话就用"->"来访问
struct book book1 = {.name = "今天你学习了吗",.number = "2022-5-28-6666"};
//这里我只想对书名和版号初始化
3)结构体大小的计算
首先我们需要知道,因为结构体是不同的数据类型集合,所以结构体大小的计算就不想数组那么简单的用元素的大小×元素的个数,结构体的大小和以下的内容有关
1.平台不同: 不同的平台一些数据的大小也不一样,例如32位中一个long类型和指针变量的大小都是4个字节,而在64位平台中一个long类型和指针变量所占的大小为8个字节
2.对齐数: 结构体在内存中存储会涉及到一个内存对齐的问题,而一般来说对齐数就是数据类型本身的大小或者编译器自带的默认对齐数(vs平台的默认对齐数是8,而linux平台就没有默认对齐数).例如一个int型它的大小为4个字节,那它的对齐数就是4,而char的对齐数就是1
3.结构体对齐规则
1)结构体的第一个成员变量总是放在地址偏移量为0处
2)结构体每个成员在存放时,地址偏移量必须能整除该成员的对齐数(如果是vs平台,则按最小的默认对齐数来计算)
3)结构体的总大小必须是它所有成员中最大对齐数的整数倍
4)如果嵌套了结构体,则作为成员的结构体的对齐数按它的成员中最大的对齐数来计算
知道了以上的内容后咱们就可以来进行结构体大小的计算
例1:先拿我们上面定义的类型来试试吧
struct book
{
//书名
char name[10];//类型是char型对齐数为1,且为第一个成员应该放在偏移量为0的地址处
//书的价格
int price;//类型为int对齐数为4
//书的版号
char number[20];//类型为char对齐数为1
};
经过以上简单的分析我们知道了3个元素的对齐数分别是 1 4 1
现在把它放在内存中
以下是我的分析过程
第一步:
首先我们根据规则1)我们把name放在地址偏移为0的地方
name是一个数组占10个字节的大小
第二步:
第2个成员是price对齐数为4所以要放在能整除4的地址偏移量处name占了10个位置,下一个位置的地址偏移量是10,显然不是4的倍数,,所以我们需要继续往后走,一直到偏移量为12的位置,12可以整除4,所以我们把price放在偏移量为12的地方
第三步:
最后一个成员是number是- -个数组占20个字节,元素是char类型所以对齐数为1,而任何非0的正整数都能整除1,所以直接放在下一个位置就可以
第四步:
当我们放入所有成员后,此时大小为36,而根据规则3),结构体book中最大的对齐数是price的4,而36刚好是4的整数倍所以这个结构体大小就是36
下面我们把它放在vs中看一下结果吧
#include <stdio.h>
struct book
{
//书名
char name[10];//类型是char型对齐数为1,且为第一个成员应该放在偏移量为0的地址处
//书的价格
int price;//类型为int对齐数为4
//书的版号
char number[20];//类型为char对齐数为1
};
int main()
{
int sz = sizeof(struct book);
printf("结构体book的大小为\n%d", sz);
return 0;
}
运行结果如下
如此看来咱么分析的没毛病呀
那我们再来看看难一点的吧
struct animals
{
char dog;
unsigned long cat;
unsigned short pig;
char fox;
};
我们先分析一下,dog是char类型所以它的对齐数是1,而它又是第一个成员所以等会要放在地址偏移为0的地方
cat是一个long类型,long在32位平台是4个字节,64位平台是8个字节所以要分情况,它的对齐数为4/8
pig是一个short类型对齐数是2
fox也是一个char类型对齐数是1
第一步:
首先放dog在偏移量为0的地方,dog只占1个字节
第二步:
cat的对齐数为4,而下一个位置的偏移量为1,所以cat不能放在这要往后走
当地址偏移量为4时,刚好能放cat,而cat占4个字节
第三步:
pig是short对齐数是2,下一个位置偏移量是8,刚好是2的倍数,所以直接放pig
占2个字节
第四步:
fox的对齐数是1,我们直接放在下一个位置就行,占一个字节
第五步:
现在这个结构体大小为11,而根据规则3),animals的最大的成员对齐数是cat的4,而11并不是4的整数倍所以我们还要向后走来到偏移量为11的位置,此时结构体大小才为12
最后:
如果在32位平台,这个结构体的大小为12
如果是在64位平台呢?cat的大小就应该是8,最大对齐数就是8
第一步:
首先放dog在偏移量为0的地方,dog只占1个字节
第二步:
cat的对齐数为8,而下一个位置的偏移量为1,所以cat不能放在这要往后走
当地址偏移量为8时,刚好能放cat,而cat占8个字节
第三步:
pig是short对齐数是2,下一个位置偏移量是16,刚好是2的倍数,所以直接放pig
占2个字节
第四步:
fox的对齐数是1,我们直接放在下一个位置就行,占一个字节
第五步:
现在这个结构体大小为19,而根据规则3),animals的最大的成员对齐数是cat的8,而19并不是8的整数倍,24才是,所以我们要来到偏移量为23的位置,此时这个结构体的大小刚好为24
最后:
如果在64位平台,我们分析这个结构体的大小为24
我们分析的结果,在32位平台下大小为12,在64位平台大小为24,在vs上运行一下吧!
32位下:
#include <stdio.h>
struct animals
{
char dog;
unsigned long cat;
unsigned short pig;
char fox;
};
int main()
{
int sz = sizeof(struct animals);
printf("结构体animals的大小为\n%d", sz);
return 0;
}
运行结果如下:
把平台修改成64位(博主的linux是64位的,在gcc运行一下)
然后下图可以给大家参考一下:
2.枚举类型
枚举类型是也是一种自定义类型,定义类型的方式有点像结构体,但是它和结构体完全不是一种类型
那么枚举类型是什么呢?
1)基本概念
定义:枚举类型,看名字就能理解,特点就是一一列举,它的成员叫做枚举常量,所以它定义的成员都是常量,其实相当于int类型
类型定义方式:
enum+枚举标签
{
枚举变量,
枚举变量,
…
};
用枚举类型定义月份,上面说了枚举常量其实相当于int类型,所以在定义的时候不给成员赋值的话,它的成员们就默认从0开始向后递增
enum year
{
January = 1, //如果不赋值就默认代表0,赋值后就从1开始往后递增啦
February, //2
March, //3
April, //4
May, //...
June,
July,
August,
September,
October,
November,
December, //12
};
2)枚举类型的特点
注意:
1.枚举类型的成员之间用逗号隔开
2.定义枚举类型后,成员名就相当于数字常量,不赋值默认从0开始递增,和#define一样的效果
其实枚举类型也是定义常量,而我们常用的#define也是定义常量,那我们为什么不直接用#define来定义呢?其实用枚举类型定义有很多优点
1.可以一次定义多个常量,而#define就做不到了
2.增加了代码的可读性和维护性(可以定义枚举常量代表一些特殊的值,例如switch中的case后面就能用枚举类型)
3.和#define定义比较,枚举类型定义有类型检查,更加严谨
4.防止命名污染(封装)
5.便于调试
3.联合体(共用体)
1)概念
定义:一种特殊的自定义类型
联合体也是共用体,看名字就知道,它的所有成员都共用同一块内存空间
类型定义:
union+联合体标签
{
成员变量;
成员变量:
…
};
union DATA
{
int arr;
int arr1;
};
联合体的定义方式有三种
1.和类型一起定义
union DATA
{
int arr;
int arr1;
}x1,x2;//定义两个共用体x1和x2
2.直接定义
union DATA
{
int arr;
int arr1;
};
union DATA x1;//注意结构体不能初始化,因为一次只能用一个成员
union DATA x2;
3.匿名定义
没有联合体标签的定义方式
union
{
int arr;
int arr1;
}x1;
2)联合体的特点
1.所有成员都共用一块空间
2.联合体的大小至少为最大成员的大小
3.每次只能使用一个成员
4.联合体不能初始化
我们试试同给共用体的两个成员赋值有什么结果
#include <stdio.h>
union DATA
{
int arr;
int arr1;
};
int main()
{
//先创建一个结构体变量
union DATA x1;
//先给arr成员赋值
x1.arr = 6;
printf("%d\n", x1.arr);
//再给第二个成员arr1赋值
x1.arr1 = 20;
printf("%d\n%d", x1.arr, x1.arr1);
return 0;
}
看看结果
我们发现给成员arr2赋值后,arr1也跟着改变了,所以我们使用共用体时,最好每次只用一个成员
3)联合体大小的计算
联合体的大小计算其实和结构体有些相似都要用到对齐数,但没有结构体那么复杂
规则
1.联合体的大小最少为最大成员的大小
2.联合体的大小必须为成员中的最大对齐数的整数倍
这样一看联合体的大小计算是不是很简单呢
四、总结
咱们用一张图直接总结内容
我们还有3个板块需要补齐,后续博主慢慢更新的