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

C语言进阶

数组

在基础篇说过,数组实际上是构造类型之一,是连续存放的。

一维数组

定义

定义格式:[存储类型] 数据类型 数组名标识符[下标];
下面分模块来介绍一下数组的定义部分的内容。

1、初始化和元素引用:

在这里插入图片描述
在这里插入图片描述

可以看到数组是连续存储的,然后因为每个int都是四个字节,所以存储结构一目了然。

2、 数组名的本质与数组越界异常

从上面的程序例子中我们可以看到,我们在打印数组名的时候使用的是%p的格式来打印,这表示arr实际上是数组的起始地址。
怎么理解呢?意思就是arr这个标识符名字表示的就是一个地址的常量,这也就是说arr这个标识符不可以无条件的出现在等号的左边(因为这意味着会被修改,定义的时候那不叫修改嗷,那叫初始化,是合法的)。

再来探究一下数组到底是怎么往元素里面写值的,其实这个过程涉及到了指针偏移的概念。
来看这么个例子:
在这里插入图片描述
在这里插入图片描述
我们居然打印出来了arr[3]的值,但是很明显这不是越界了吗?这数组是怎么找到arr[3]的呢?

其实这都是指针偏移造成的,这也是为什么说数组存取速度快的原因,当取a[i]值的时候,其对应的是依赖 a[i] =*(a + i) 即使用数组名加上 i 个偏移地址来取得的,所以当我们取a[3]的时候,尽管已经越界,但是我们依然能够通过偏移量 3和数组名 a来依靠指针取得正确的值(注意这个在某些优化后的编译器是肯定会报越界异常的,只不过这里gcc更加开放而已嗷)。

二维数组

我们将从以下几点来说明二维数组。
1、定义以及初始化
格式为:[存储类型] 数据类型 标识符 [行下标][列下标];

初始化时行标是可以没有的,因为靠列标也能知道会分成几行呀。

#define M 2
#define N 3int arr[M][N];

上面的程序就定义了一个二行三列的行列式,其从人类的逻辑上理解是下图这样的:
在这里插入图片描述
也就是一个方格的形式,但实际上其在计算机中存储依然是线性的一个数组:
在这里插入图片描述
也就是说第一行和第二行是连在一起的,第一行的最后一个元素地址再偏移一个就到了第二行行首的位置。

BTW,二维数组不过是一维数组的一个拓展,所以一维数组的特性二维数组都有,比如如果不初始化的话都是数组元素都是随机值,除了可以动态赋值之外,二维数组也可以进行静态初始化和部分初始化:

//动态赋值
for(int i = 0; i< M ;i++)for(int j =0; j < N;j++)scanf("%d",&a[i][j]);//静态初始化
int a[M][N] = {{1,2,3},{4,5,6}};
/* 打印结果
1 2 3
4 5 6
*///局部初始化
int a[M][N] = {1,2,3};
/*打印结果
1 2 3
0 0 0
*///局部初始化
int a[M][N] = {{1,3},{6}}
/*打印结果
1 3 0
6 0 0
即已经初始化的位置就初始化为该值,其余未被初始化的位置就全为0
*///不初始化
int a[M][N];
/*
434223423 324234 324324
213123 21312321213 3432432
如果不初始化一样是一堆乱码
*/

2、元素引用

之前已经学习过:
数组名 [行标][列标];

3、深入理解二维数组

我们在上面用过这样的初始化语句:

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

实际上其在内存中的存储是这样的,a[0]表示第一行元素(可以看作是一个小数组)a[0][0]的首地址,a[1]表示第二行元素(同样可以看作是一个小数组)a[1][0]的首地址,只不过这两个小数组是连续存储的而已,这也就意味着对数组名a+1,其跳转的应该是第二行元素的行首地址:
在这里插入图片描述
这就是行指针的含义,在指针专题我们还将深入理解指针这个东西,还会再提这块内容。

字符数组

对于字符数组我们从下面几个方面进行学习:

1、定义以及初始化还有存储池特点

[存储类型] 数据类型 标识符[下标] …(可以多个下标,变成多维字符数组嘛)

对于字符数组的初始化,我们可以用单个字符初始化,也可以用字符串常量来初始化。
单个字符初始化:
在这里插入图片描述
在这里插入图片描述

字符串常量初始化,注意对于这一点是其区别于前面各种数组的关键,因为对字符串操作时其有一个存储上的特点,它会多出一个尾标记,即尾0,这用来标记当前字符串的一个结束。

比如我们有一个字符串"hello"共五个字符要存储,实际上它在数组当中存的是:h e l l o \0
共六个字符。

在这里插入图片描述
在这里插入图片描述
上面的代码相当于做了一个部分初始化,即数组长度为3,第一个元素是a,第二个元素和第三个元素都被默认置成尾0了.

2、输入输出

另外我们也可以使用gets函数来输入获取一个str,只不过这个函数之前说过不太安全,所以要慎用嗷。

之前的scanf和printf也可以用在字符数组的输入输出中:
在这里插入图片描述
但是scanf依然存在的问题是其不能输入一个带有分隔符的串,这一点使用的时候要注意。

连续写入的方式:
在这里插入图片描述
输出效果:
在这里插入图片描述
3、常用函数

使用字符串相关的函数的时候 ,要包含头文件string.h。
使用这些函数可以给我们使用字符数组的时候带来方便。
如下:

strlen 和 sizeof
strcpy 和 strncpy
strcat 和 strncat
strcmp 和 strncmp

1、 strlen 和 sizeof
strlen返回当前字符数组的长度:
在这里插入图片描述
而sizeof返回的是当前数据所占内存的字节数大小,我们来测试一下:
在这里插入图片描述
在这里插入图片描述
字符串长度为5在strlen函数的说明中就已经说过该函数计算字符串长度时不会加上尾0,所以自然为5,而字节数为6则证明了尾0也是一个字符,也要在内存中占据一个字节的大小嗷。

为了加深印象,再来一个例子:
在这里插入图片描述
在这里插入图片描述
可以看见strlen计算长度是依靠尾0的,上图程序中计算长度只到尾0就结束了,所以长度为5,而字节数可以看到为10,因为有两个尾0的存在。

2、 strcpy 和 strncpy
之前说过数组名在被初始化之后是不可以作为左值在赋值号左边出现的,那我们想给一个已经初始化过的串进行赋值怎么办?
就可以使用strcpy:
在这里插入图片描述
来测试一下:
在这里插入图片描述
在这里插入图片描述
而对于strncpy也是差不多的,它只是多了一个拷贝数量大小的参数,该参数表示要从源串拷贝多少字符到目标串中,一种最佳实践是将该参数设置为与目标串一样的大小,这样可以防止数组越界异常:
在这里插入图片描述
在这里插入图片描述
3、strcat 和 strncat
strcat的作用是连接:
在这里插入图片描述
很明显就是把两个串接起来嘛,来测试一下:
在这里插入图片描述
在这里插入图片描述
而strncat也是类似的,只是多了一个数量参数,其表示最多从源串里面取该参数数量个字符连接到目标串中。
如果源串字符数量不足该参数数量个字符,则取到尾0为止。
在这里插入图片描述
在这里插入图片描述
4、strcmp 和 strncmp
strcmp用来进行比较:
在这里插入图片描述
如果没有该函数的话,我们进行字符串的比较将是非常困难的(或者说是麻烦的)。
我们来测试一下:
在这里插入图片描述

在这里插入图片描述
可以看到如果相等的话返回的就是0,如果不相等的话,将返回两个串中首个不相匹配的asc码的差值,如上图程序中h的asc码为104,而w的asc码为119,str1减去str2正好为-15,所以不相等,相等的话返回值为0。

strncmp也是类似,多一个数量参数n,其表示只比较这两个串的前n位的字符是否相等。

在这里插入图片描述

在这里插入图片描述

指针

变量与地址的关系

什么是变量,什么又是地址?
先来看一行代码:

int i = 1;

这是一句变量的声明与定义(或者说定义变量与初始化,前面那是C++里的叫法)。

我们测试过,如果打印其地址和值的话,有如下:

&i = 0x21312321
i = 1

这表示了在当前0x21312321这块地址空间存放了 1这样一个值:
在这里插入图片描述

这相当于有一块内存地址空间,我们使用0x21312321这个标记来标识了这块内存地址空间,然后在里面存放了 1这个值(补码形式),这块空间的内容改成10、100 、1000都是没问题的。所以变量与地址的概念自然就出来了:

变量其实就是给用户用的,它是当前对某一块内存空间的抽象命名,我想把这块内存空间存储的内容修改成100就让 i = 100,而这块内存空间有一个绝对的名字(比如0x123213)就叫地址,这是给编译器用的,编译器通过它来查找这块内存空间从而进行对其内容的读写操作。

而我们所谓的指针其实就是地址,也就是说指针等价于地址。

指针与指针变量

那么啥又是指针和指针变量呢?
比方说有一个整形数3,我们现在想把这个3保存起来是不是就得用到一个整形变量,那么同理,假如有一个地址值,我们想把这个地址值给保存起来,此时就需要一个地址变量,又因为之前说过其实地址就是指针,所以就有了指针变量,我们存放进该变量中的地址值就是指针。

所以我们日常说的”让某指针指向哪里指向哪里“严格地说是不对的,因为指针本身就是地址,是一个常量是不能做左值的,我们所谓的改变指向其实指的是指针变量所存放的指针被改变了,比如某指针变量存放的是0x2000,那么该指针变量就指向0x2000,若改变了存放的变成了0x3000那么该指针变量就指向0x3000,是这个意思。

继续来深入,看一下嵌套的指针之间是什么关系。

int i=1;

还是先定义并初始化一个变量 i,其值为1,这句代码表示在内存当中OS将会分配一块内存空间给这个变量 i,其地址假设为0x1000:
在这里插入图片描述

现在我们定义一个p指针,该指针用来指向变量 i 的地址:

p = &i;

但很明显缺少一些东西是吗,i的地址是一个指针,所以我们需要一个变量,已经设置为p了,为了能够保存指针数据,说明该变量必须为指针变量:

int* p = &i;

这样是不是完整了?指针变量p保存了整形变量 i的地址值,但是只要是变量就有地址嗷,假设指针变量 p地址为0x2000:
在这里插入图片描述
现在有趣的事情来了,既然指针变量也有地址,我们是不是依然可以继续存放?
答案是肯定的:

q = &p;

还是分解着来看,对于指针变量p的地址值,我们应该使用什么类型去保存该地址呢?多加颗星就好了:

int** q = &p;

int*表示指针变量,然后int**就表示指针变量的地址值啦:
在这里插入图片描述

从图上明显可以看出来,我们依然可以推出这个二级指针肯定也是有其地址值的,上图假设为0x3000.
总结:
i 的值为 1;
&i 的值为0x1000;
p 的值为0x1000;
&p 的值为0x2000;
*p 的值为1;
q 的值为 0x2000;
&q 的值为 0x3000;
*q 的值为0x1000,也就是&i
**q 的值为1,也就是*(&i),为1

有了上面这些关系之后,我们想要访问变量 i就可以有两种方式来访问:
1、通过 i
2、通过 *p
这就引出了直接访问和间接访问的概念。

直接访问与间接访问

我们通过 i 来访问改变内存空间就称为直接访问;
而通过指针变量 p 的关联作用来访问改变变量 i 的值则称为间接访问(或者说一级间接访问,因为我们通过q也能够间接访问到i,这就成了二级间接访问)。

空指针与野指针

空指针:

int* p = NULL;

指针定义出来,但是还不清楚它具体要指向谁怎么用,此时就可以先将它指向NULL表示空,这个NULL 是define出来的一个宏,值为0,也就是在让指针指向起始地址为0的一块的空间,这样做的好处是如果指针进行了非法操作我们能够得到一个段错误的提示。

因为我们在当前系统上规定了0号这块空间不分配给任意一个进程,所以如果我们企图去写这样一块空间的话系统当然会给我们报错。

我们把指针置为空是为了防止野指针的产生,什么是野指针?

野指针的意思某一个指针的指向不确定或者说压根就没有指向,然而我们直接使用了这个指针,这就很可能会造成严重的问题。

空类型的指针

所谓空类型的指针就是指 void*;
对于void* ,任何类型的指针值都能够把自己的值赋值给void*,void也能够把当前自己的值赋值给任何类型的指针。
有一种特殊情况,void
和一个函数指针之间来互相赋值就这一种情况是在C99标准当中没有定义的,也就是未定义行为。

定义与初始化的书写规则

这些在上面的讲解中都是写过了的,比较简单这里就不再赘述。

指针运算

指针所能执行的运算有:* 、 & 、关系运算(如比大小,指针比的是地址的大小,即两个指针的地址值的高低;还有比如++、–运算等)。

指针与数组的关系

指针与一维数组

这个就没啥好说的,因为之前已经说过很多了。

在这里插入图片描述
其实不难推出还有如下关系:&a[i] = a+1 = p+1 = &p[i],编译运行如下:
在这里插入图片描述
可以看到使用指针和数组名的效果是等价的。

实际上从上面的代码中我们也可以看出,事实上数组和指针都是可以互换着使用的,那么数组与指针唯一的区别是什么呢?

唯一的区别就是:数组名是常量,而上面的指针 p 是个变量。
在这里插入图片描述
像上图这么写,是不是看起来和数组别无二致,只不过是个匿名数组罢了,因为其没有数组名。

指针与二维数组

在这里插入图片描述
上面p = a为什么是错的问题在数组指针与指针数组那一节会进行讲解。

指针与字符数组

在这里插入图片描述
在这里插入图片描述
这个和我们之前说的其实没什么不同,有不同的地方主要在下面一些地方:
在这里插入图片描述

在这里插入图片描述
可以看到我们打印其sizeof所占字节数的时候为8,这是指针所占字节数(64位环境),然后长度为5这个和字符数组是一样的。
我们继续看:
在这里插入图片描述
现在我们试图对指针进行和对字符数组一样的拷贝操作,会发现存在段错误:
在这里插入图片描述
这是因为对于字符数组而言(上面代码没写,但是之前是有写过的,自己脑部一下叭),是把"world"的内容拷贝到str的地址以及其后面的地址中,对于字符数组的sizeof我们可以得到其所占字节数为6(hello+一个尾0字符)个字节,所以拷贝过程其实是用w覆盖h,o覆盖e,以此类推,正因为字符数组本身就是有内容的我们只不过是用strcpy函数来进行覆盖写所以对于字符数组使用strcpy没有问题。

而我们使用指针形式的strcpy报段错误,是因为使用”world“覆盖到的str,其所指向的是一个字符串常量,企图使用"world"去覆盖一个串常量这肯定是错误的,串常量在当前的存储位置是特殊的,在使用上不允许被改变和覆盖。

其实我思考了一下可能和内存分配有关系,对于字符数组形式的hello,是一串连续的地址空间来依次存放char类型的字符:
在这里插入图片描述
而对于指针形式的hello,则是单独使用一块内存空间来存放整个字符串:
在这里插入图片描述
正是因为这种存储方式不同的原因,所以对于字符数组进行copy写覆盖的时候一一对应是很容易做到的,对于字符串常量这种则不然,因为各个字符都并到一起了还怎么进行单个单个的写覆盖,所以必然报错。

那我们还是想改变指针str的指向内容咋办,直接改指向嘛:

str = "world";

相当于此时开辟了另一块空间来存放world这个字符串,然后指针str指向该空间即可。

const与指针

指针常量和常量指针已经说过太多了,不再赘述。

指针数组与数组指针

数组指针:本质是一个指针,指向一个数组,定义方式如下;

【存储类型】 数据类型 (*指针名)[下标];
[auto] int (*p)[3];

这样的写法其实不好理解,我们可以将定义方式抽象成 type name 这样的风格来想(但别这样写,编译器不认嗷),首先我们需要一个指针p,那么这个指针的数据类型是什么,那不是就是int[3]*吗,所以数组指针就是:int[3]* p;

这样的含义是数组指针p指向了一个 元素为三个的数组 的这种数据类型的起始位置,原来为int的时候指针p做+1操作指的是移动一个int类型的地址大小,而现在变成int[3]之后,指针p+1则表示的是一下移动三个int类型的地址大小(联系我们之前说的二维数组中的行指针就好懂了)。

这和我们上面在二维数组那一节提到的数组名a(a是二维数组的行指针)是一样的,数组名a其实就是一个数组指针,而p就是一个普通的指针,所以我们直接让p = a是错误的,指针类型无法匹配。

指针数组:本质是一个数组,这个数组存的是指针,定义方式如下:

【存储类型】 数据类型* 数组名[长度];
int* arr[3];

把数组指针的括号给去掉就成了指针数组了。
来简单测试一下:
在这里插入图片描述
在这里插入图片描述

多级指针

多级指针其实之前也提过了(还记得之前的二级指针吗),掌握到二级指针就已经够用了,这里不再赘述。

函数

函数的定义

定义格式如下:

数据类型 函数名(【数据类型 形参名,数据类型 形参名, ...);

最经典的例子就是我们的main函数:
在这里插入图片描述
除了main函数之外,其它的函数如下图:
在这里插入图片描述

main函数上面的叫函数声明,函数下面的叫函数定义(实现)。

函数的传参

值传递

在这里插入图片描述
这个之前也说过太多了,不再赘述。

地址传递

在这里插入图片描述
不再赘述。

函数的调用

函数的嵌套调用

在这里插入图片描述

递归

不再赘述。

函数与数组

函数与一维数组

先来看一维数组传参与不传参时的一个小差异:
在这里插入图片描述
在这里插入图片描述
可以看到main函数中的数组长度为20,这是很自然的,因为int类型在32位机器上占四个字节,那五个int类型的数肯定占20个字节。
而对于传递给函数的形参数组名来说则意义不一样了,函数的形参只不过是用指针类型来接收了一个地址值(数组名就是个地址),所以在32位机器上一个指针类型的变量占8个字节,这是二者打印出来的sizeof大小不同的原因。

所以这就存在一个问题,在数组传参时,拥有形参的函数由于只拥有该数组的起始地址,所以它并不知道这个数组有多大,甚至它根本不知道这是一个数组,所以传递数组的时候,还应该传递其数组长度:
在这里插入图片描述
在这里插入图片描述
另外接收数组形参的时候,除了上述写法,也还有另外一种方式:

void printf_arr(int p[],int n);

但注意这种形式的写法和int* p是一样的,也就是说这种写法在定义时和形参时所代表的含义是不一样的,在形参时这代表就是一个指针,在定义的时候则表示其为一个数组。

函数与二维数组

在这里插入图片描述

在这里插入图片描述
这种写法不难理解,就是将这个3行4列的数组拿来当一个大数组来使用了,这种写法中的实参还可以写成*a,a[0],*(a+0)等。

如果还是希望以行列形式来打印的话,可以使用之前说过的数组指针的形参形式来接收实参:
在这里插入图片描述

在这里插入图片描述
可以看到正常打印输出,另外我们还能注意到在main函数中a的大小为48字节,这是因为12个整形元素在32位系统环境下就是12*4=48个字节,而对于p来说,还是和之前一样的,p是一个数组指针,本质是一个指针指向了一个拥有三个整形元素的数组,因为一个指针在32位系统环境下占8个字节,所以其打印出来结果为8。

和一维数组一样,我们依然可以写成下面这种形参形式:

void printf_arr1(int p[][N],int m,int n);

但这其实本质上就是一个数组指针,其所占字节数依然为8。

函数与字符数组

在这里插入图片描述
在这里插入图片描述

函数与指针

指针函数

指针函数本质是个函数,只不过返回值是个指针,定义形式为:

返回值* 函数名(形参列表);
如:
int* fun(int i);

函数指针

函数指针的本质是一个指针,其指向一个函数,定义形式为:

类型 (*指针名)(形参列表);
如:
int (*p)(int i);

在这里插入图片描述
在这里插入图片描述

函数指针数组

在上面程序的基础上,我们还能再写一个减法的函数,然后用函数指针来引用:
在这里插入图片描述

在这里插入图片描述
我们可以看到,函数指针p和q长得一模一样,那我们是不是可以搞一个函数指针数组来存储这两个指针呢?
当然可以,函数指针数组的定义形式如下:

类型 (*数组名[下标])(形参列表);
如:
int (*arr[N])(int i);

这个表达式意思是:arr是一个数组,数组当中有N个元素,这N个元素都是指向一个只具有一个int形参的函数的指针。

那么上面的程序就可以改写成:
在这里插入图片描述
在这里插入图片描述
在这些基础上,还有更离谱的套娃概念:
指向指针函数的函数指针数组:

int *(*funcp(N))(int);

构造类型

结构体

类型描述

形式如下:

struct 结构体名{数据类型 成员1;...
};

定义结构体时要注意,大括号后的分号不能丢,另外结构体作为一种类型描述是不占用任何存储空间的,所以无法在说明完成员数据之后就直接接等号给其赋值进行初始化(没有任何存储空间那初始化肯定就没有意义呀)。

嵌套定义

在这里插入图片描述

定义变量(变量、数组、指针),初始化以及成员引用

在这里插入图片描述
对于成员变量向上面这样赋值即可,引用则使用变量名.成员名的方式。
在这里插入图片描述
嵌套类型的定义初始化以及成员引用如上图。
我们还可以只初始化部分结构体中的元素内容:
在这里插入图片描述
如果是指针的话,那么需要用指针->成员名的方式来进行成员引用(也可以(*指针).成员名),简单示例:
在这里插入图片描述
数组形式的结构体:
在这里插入图片描述

结构体在内存当中所占用的内存大小

先来看这样一个例子:
在这里插入图片描述
在这里插入图片描述
分析一下,在64位系统下我们知道指针变量是占8个字节的,但是为什么结构体变量会是12个字节呢?
int 占4个字节float占4个字节,char占一个字节,加起来也就9个呀?这会和变量声明的顺序有关吗,我们将float与char互换顺序会发现一样是12个字节。

再加一个char呢:
在这里插入图片描述

在这里插入图片描述
结果依然不变,但是我们再变化一下,让一个char型放到float下面去:
在这里插入图片描述
在这里插入图片描述
会发现我们的结构体所占字节数竟然飙升到了16个,这是为什么呢?

这是因为结构体具有一个对齐情况。

这是由硬件决定的,因为硬件存储有不同的形式,如半字存储、字存储、双字存储等,为了方便硬件进行指令操作所以结构体这种变量天然的就存在一个地址对齐的机制。当然我们不学硬件的话很难从硬件角度来思考这个问题,所以我们从软件角度笼统的分析一下。

对于上面的示例程序,第一个int变量就相当于一个标尺这样的概念(这实际上取决你当前所使用的机器字长),如果是int类型的整倍数那么没有问题,该咋存就咋存,比如double。但是如果当前数据类型的存储大小比int值要小,那么此时要进行地址偏移,也就是说存完了当前变量之后要跳过多少地址去存储下一个变量。

所以int占四个字节,然后char型虽然只占一个字节但是不满四个字节也要占4个字节,float也要占四个字节所以共12个字节。

我们刚刚还试过:

int i;
char ch;
char ch1;
float f;

这种形式,会发现也只占12个字节,这是因为ch变量占的四个字节还可以容纳下一个char型一字节变量,所以总共还是十二字节。

但是当我们将一个char类型放到最下面的时候:

int i;
char ch;
float f;
char ch1;

此时占16个字节,知道为什么了吧?因为最下面的ch1变量也要占四个字节,所以共16个字节。

这是存在地址对齐的情况,如果我们不想让编译器对其进行地址对齐,那么我们可以在结构体上使用下面的语法:
在这里插入图片描述
在这里插入图片描述
此时可以发现该结构体所占内存数大小就正常了:
在这里插入图片描述

这里还要再聊一个函数传递结构体类型参数的问题,不难推敲出,如果使用值传递的话,那么对应函数的形参将也要开辟出和实参对应的字节数空间大小(上面的例子就是十二个字节),这很浪费空间,但是传递指针的话在64位系统环境只占8个字节,所以使用地址传递能够减少不必要的内存消耗。

共用体

union 共用体名{数据类型 成员名1;数据类型 成员名2;...
};

简单的示例:
在这里插入图片描述
对于共用体来说,共用体实际上就是一块空间,空间大小按照成员变量中最大的来决定,这块空间谁爱用谁,不用就滚蛋。

在这里插入图片描述
在这里插入图片描述
因为此时成员变量中最大的是double类型为8个字节,所以其共用体大小为8,我们将double类型删去,则其共用体大小应该为4(即 int类型的字节数):
在这里插入图片描述
所以这也就意味着,哪个成员要使用这块空间就指使用哪个成员,千万别混着用,不然会出现很多问题(因为各个类型的存储方式不同):
在这里插入图片描述
在这里插入图片描述
可以看到,只有 i 是正常输出的,对于另外两个值我们并没有进行定义,但依然输出了值是因为此时这块共用体空间的四个字节已经被写入了整形类型的100的补码,所以当使用其它类型来输出的时候就出现了该补码所对应的该数据类型的值,这再次印证了共用体的所有成员变量所使用的空间是同一块,并且这块空间的大小为这个共同体中数据类型所占字节数最大的类型的字节数。

如果是指针的话也和结构体一样,可以使用箭头来引用成员变量:
在这里插入图片描述

其它的内容共用体和结构体是一样的,只需要注意一下上面所说的和结构体有区别的地方,还有一个部分也不太一样,是共用体中的位域概念。

值得注意的是,结构体和共用体是可以相互嵌套定义的。

位域

这个机制不是搞硬件的话并不重要,知道有这回事就好了。
它是共用体的另一种形式,与共用体的区别就是它存放变量时并不以字节为单位,而是以位为单位:
在这里插入图片描述

上图中表示char型的a只占一个位,b占两位,c占一位,也就是说上图中的union所占字节数为一个字节(因为char类型的变量y占一个字节,比结构体中的各个变量abc都大)。

不懂也没关系,这个没啥用。

枚举

enum 标识符{成员1;成员2;...
};

示例:
在这里插入图片描述
在这里插入图片描述
可以看到,对于枚举结构来说,如果不赋初始值的话,那么默认从上往下是从0开始往后依次赋值的。

如果对其中一个初始化,那么顺下的都会依次自动加1:
在这里插入图片描述
在这里插入图片描述
如果是初始化了中间的某个值,则又从该值开始向后依次+1进行排列:
在这里插入图片描述
在这里插入图片描述

动态内存管理

动态内存管理一般原则:谁申请谁释放。在这里插入图片描述

malloc

对于malloc函数,输入参数是需要多少个字节空间,然后该函数从堆空间上返回这块空间的起始地址,其类型为void*,表示可以是任意类型的指针来接收它。
在这里插入图片描述

calloc

对于calloc函数,其参数有两个,表示若某一个数据成员是size个字节大小,则该函数从堆空间上申请一块能存放nmemb个这种类型的数据成员的空间地址返回给函数的调用者。

realloc

对于realloc函数,其表示需要重新分配一块动态内存空间,参数有两个,从man手册上可以知道参数 ptr 必须是一个已经由malloc或者calloc分配后返回的指针,所以这个函数的含义就是其原来已经由malloc或者calloc函数分配过一块空间,起始地址为ptr,但是现在空间大小不太合适了,此时就需要使用realloc进行重新分配:从ptr这个地址开始,我要size个字节的空间。

比如一开始我们申请了100个字节的空间,但是用着用着发现不够用了,此时使用realloc函数申请原来的空间变为300个字节大小,那么realloc函数将会从原来的地址开始继续往下再连续分配200个字节以补足300个字节,然后返回这300字节大小空间的起始地址,为什么还需要返回起始地址?因为有时候如果原始的地址已经没有(或者说不满足需要)足够的连续空间的话,realloc函数将从其它位置寻找一块能够满足所需要的连续空间地址,此时新分配的地址与原地址不同,所以需要返回新空间的起始地址。

free

对于free函数,我们都知道是对一块已经分配了的空间进行释放,但是我们要正确理解这个释放:
1、free了之后的空间不能再使用
2、free了之后的空间并非不存在了,依然存在

我们的free并没有把原来使用的这块内存给扣掉,也没有把内存清掉,甚至指针的指向还可能指向该位置,但它一定是个野指针。
free后只是代表着我们的指针对于某一块内存空间不再具有指向或者说引用权限了,此时该空间被系统回收,而指针就没有了指向成为了一个野指针。

来验证一下这个事情:
在这里插入图片描述
在这里插入图片描述
可以看到我们在free掉p之后,依然可以对该指针进行操作,然后打印出来的 p地址还是原来free之前的地址,我们依然可以对其进行读写操作,这是很危险的事情,说不定该指针就指到了哪些空间程序就会出问题,所以一种最佳实践是,在free掉之后又暂时不知道该指针要指向哪里,那么就先将其置空:
在这里插入图片描述
在这里插入图片描述
可以看到此时就会出现段错误,有效终止了问题的扩大化。

typedef关键字的使用

typedef是为已有的数据类型改名。

typedef 已有数据类型 新名字;

简单使用一下:
在这里插入图片描述

这样做的好处是,如果以后我想改变 i的数据类型,那么我只要去typedef的位置改变int类型为我想要的类型即可,非常的方便。

那么这与我们的#define宏定义有什么区别?看起来似乎是一样的。

区别存在于复合类型上的一些声明上:
在这里插入图片描述
比如int这种复合类型,从上图我们可以看出,在连续定义两个指针变量p和q时,因为宏定义只是简单的文本替换,所以对于q来说它只会定义一个整形变量,而对于typedef来说则会严格的定义一个int的指针变量q。

typedef还可以给如下形式改名:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
还可以给函数进行typedef:
在这里插入图片描述

相关文章:

C语言进阶

数组 在基础篇说过&#xff0c;数组实际上是构造类型之一&#xff0c;是连续存放的。 一维数组 定义 定义格式&#xff1a;[存储类型] 数据类型 数组名标识符[下标]; 下面分模块来介绍一下数组的定义部分的内容。 1、初始化和元素引用&#xff1a; 可以看到数组是连续存储…...

Linux之gdb

gdb就是一个Linux的调试工具&#xff0c;类似与vs里面的调试 可执行程序也有格式&#xff0c;不是简单的二进制堆砌...

100天精通风控建模(原理+Python实现)——第3天:风控建模中如何处理缺失值?

风控模型已在各大银行和公司都实际运用于业务,用于营销和风险控制等。    之前已经阐述了100天精通风控建模(原理+Python实现)——第1天:什么是风控建模?    100天精通风控建模(原理+Python实现)——第2天:风控建模有什么目的?    接下来看下100天精通风控建模(原理…...

Leetcode—680.验证回文串II【简单】

2023每日刷题&#xff08;二十七&#xff09; Leetcode—680.验证回文串II 实现代码 class Solution { public:bool judgeFunc(string s, int left, int right) {while(left < right) {if(s[left] ! s[right]) {return false;}left;right--;}return true;}bool validPalin…...

Redis五种数据类型及命令操作(二)

&#x1f388;个人公众号:&#x1f388; :✨✨✨ 可为编程✨ &#x1f35f;&#x1f35f; &#x1f511;个人信条:&#x1f511; 知足知不足 有为有不为 为与不为皆为可为&#x1f335; &#x1f349;本篇简介:&#x1f349; 本篇记录Redis五种数据类型及命令操作&#xff0c;如…...

低代码信创开发核心技术(三):MDA模型驱动架构及元数据系统设计

前言 写最后一篇文章的时候&#xff0c;我本人其实犹豫了半年&#xff0c;在想是否发布出这篇文章&#xff0c;因为可能会动了很多人的利益。所以这篇文章既是整个低代码信创开发的高度总结&#xff0c;也是最为精华的一部分&#xff0c;它点明了低代码中最为核心的技术。虽然…...

HslCommunication模拟西门子读写数据

导入HslCommunication C#端代码&#xff08;上位机&#xff09; 这里要注意的是上位机IP用的当前电脑的IP。 using HslCommunication; using HslCommunication.Profinet.Siemens; using System; using System.Collections.Generic; using System.ComponentModel; using Syste…...

多测师肖sir_高级金牌讲师_ui自动化po框架版本02

ui自动化po框架版本02 一、 pages下的BasePage.py模块 此模块是封装所有用例的基类 比如说&#xff1a;所有用例要用到的元素定位&#xff0c;以及输入框输入&#xff0c;点击&#xff0c;下拉等等公共方法import unittest #导入unittest 框架 from time import *# 调试代码…...

线性判别分析(Linear Discriminant Analysis,LDA)

Linear Discriminant Analysis&#xff08;LDA&#xff09; 输入&#xff1a; 原始数据$D((x_1,y_1),(x_2,y_2),...,(x_m,y_m)$ 、​ 类别标签$Y[y_1,y_2,...,y_n]$、​ 降维到的维度d输出&#xff1a; 投影矩阵W、投影后的样本$Z$、算法步骤&#xff1a; 1.计算类内散度…...

git的分支及标签使用及情景演示

目录 一. 环境讲述 二.分支 1.1 命令 1.2情景演练 三、标签 3.1 命令 3.2 情景演示 ​编辑 一. 环境讲述 当软件从开发到正式环境部署的过程中&#xff0c;不同环境的作用如下&#xff1a; 开发环境&#xff1a;用于开发人员进行软件开发、测试和调试。在这个环境中…...

深度解析找不到msvcp120.dll相关问题以及解决方法

​在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“msvcp120.dll丢失”。这个错误通常会导致某些应用程序无法正常运行&#xff0c;给用户带来很大的困扰。那么&#xff0c;如何解决msvcp120.dll丢失的问题呢&#xff1f;本文将为大家介绍…...

SQL Server 2022 安装步骤——SQL Server设置身份验证教程

目录 前言: 安装详细步骤: 第一步: 第二步: 第三步: 第四步: SQL Server 连接的方式: Window验证: SQL Server验证: 两者之间区别: 总结: SQL Server身份验证登录配置教程:​ 第一步: 第二步: 第三步: 番外篇: 前言: 本文讲解&#xff0c;如何安装SQL Server安…...

Maven各方面配置好了却无法显示版本

今天配置了maven环境&#xff0c;各方面都配置好了命令行却一直没办法显示maven的版本&#xff0c;原因 竟是两个JDK导致maven无法选择&#xff0c;因为maven依赖于JDK&#xff0c;导致在选择JDK的时候差生了二义 性&#xff0c;在环境变量里面删除不常用的JDK&#xff0c;只…...

Jdk 1.8 for mac 详细安装教程(含版本切换)

Jdk 1.8 for mac 详细安装教程&#xff08;含版本切换&#xff09; 官网下载链接 https://www.oracle.com/cn/java/technologies/downloads/#java8-mac 一、选择我们需要安装的jdk版本&#xff0c;这里以jdk8为例&#xff0c;下载 macOS 版本&#xff0c;M芯片下载ARM64版本…...

02MyBatisPlus条件构造器,自定义SQL,Service接口

一、条件构造器 1.MyBatis支持各种复杂的where条件&#xff0c;满足开发的需求 Wrapper是条件构造器&#xff0c;构建复杂的where查询 AbstractWrapper有构造where条件的所有方法&#xff0c;QueryWrapper继承后并有自己的select指定查询字段。UpdateWrapper有指定更新的字段的…...

c语言练习11周(6~10)

输入任意字串&#xff0c;将串中除了首尾字符的其他字符升序排列显示&#xff0c;串中字符个数最多20个。 题干 输入任意字串&#xff0c;将串中除了首尾字符的其他字符升序排列显示&#xff0c;串中字符个数最多20个。输入样例gfedcba输出样例gbcdefa 选择排序 #include<s…...

钉钉API与集简云无代码开发连接:电商平台与营销系统的自动化集成

连接科技与能源&#xff1a;钉钉API与集简云的一次集成尝试 在数字化时代&#xff0c;许多公司面临着如何将传统的工作方式转变为更智能、高效的挑战。某能源科技有限公司也不例外&#xff0c;他们是一家专注于能源科技领域的公司&#xff0c;产品包括节能灯具、光伏逆变器、电…...

C++算法:包含三个字符串的最短字符串

涉及知识点 有序集合 字符串 题目 给你三个字符串 a &#xff0c;b 和 c &#xff0c; 你的任务是找到长度 最短 的字符串&#xff0c;且这三个字符串都是它的 子字符串 。 如果有多个这样的字符串&#xff0c;请你返回 字典序最小 的一个。 请你返回满足题目要求的字符串。…...

华为开源carbondata中的使用问题处理

carbondata中的使用问题处理 Q&#xff1a;什么是不良记录&#xff1f; A&#xff1a;由于数据类型不兼容而无法加载到CarbonData中的记录或为空或具有不兼容格式的记录被归类为不良记录。 Q&#xff1a;CarbonData中的不良记录存储在哪里&#xff1f; A&#xff1a;不良记录…...

AI:76-基于机器学习的智能城市交通管理

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

OpenLayers 分屏对比(地图联动)

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能&#xff0c;和卷帘图层不一样的是&#xff0c;分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

基于matlab策略迭代和值迭代法的动态规划

经典的基于策略迭代和值迭代法的动态规划matlab代码&#xff0c;实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...

es6+和css3新增的特性有哪些

一&#xff1a;ECMAScript 新特性&#xff08;ES6&#xff09; ES6 (2015) - 革命性更新 1&#xff0c;记住的方法&#xff0c;从一个方法里面用到了哪些技术 1&#xff0c;let /const块级作用域声明2&#xff0c;**默认参数**&#xff1a;函数参数可以设置默认值。3&#x…...

Unity VR/MR开发-VR开发与传统3D开发的差异

视频讲解链接&#xff1a;【XR马斯维】VR/MR开发与传统3D开发的差异【UnityVR/MR开发教程--入门】_哔哩哔哩_bilibili...

《Offer来了:Java面试核心知识点精讲》大纲

文章目录 一、《Offer来了:Java面试核心知识点精讲》的典型大纲框架Java基础并发编程JVM原理数据库与缓存分布式架构系统设计二、《Offer来了:Java面试核心知识点精讲(原理篇)》技术文章大纲核心主题:Java基础原理与面试高频考点Java虚拟机(JVM)原理Java并发编程原理Jav…...

对象回调初步研究

_OBJECT_TYPE结构分析 在介绍什么是对象回调前&#xff0c;首先要熟悉下结构 以我们上篇线程回调介绍过的导出的PsProcessType 结构为例&#xff0c;用_OBJECT_TYPE这个结构来解析它&#xff0c;0x80处就是今天要介绍的回调链表&#xff0c;但是先不着急&#xff0c;先把目光…...

数据分析六部曲?

引言 上一章我们说到了数据分析六部曲&#xff0c;何谓六部曲呢&#xff1f; 其实啊&#xff0c;数据分析没那么难&#xff0c;只要掌握了下面这六个步骤&#xff0c;也就是数据分析六部曲&#xff0c;就算你是个啥都不懂的小白&#xff0c;也能慢慢上手做数据分析啦。 第一…...

基于Python的气象数据分析及可视化研究

目录 一.&#x1f981;前言二.&#x1f981;开源代码与组件使用情况说明三.&#x1f981;核心功能1. ✅算法设计2. ✅PyEcharts库3. ✅Flask框架4. ✅爬虫5. ✅部署项目 四.&#x1f981;演示效果1. 管理员模块1.1 用户管理 2. 用户模块2.1 登录系统2.2 查看实时数据2.3 查看天…...