这一篇我们来看一下C语言的构造类型,构造类型也是C语言的数据类型之一,其中包括:数组、结构体、联合体、枚举
一、首先,数组,我们将从数组的定义、数组的赋值、数组的长度、数组的遍历这几个方面来讨论。(这里只说一维数组)
首先我们定义一个数组,
#include<stdio.h>
void main(){
/*
在定义数组的时候,一种方式是指定数组的长度;
一种方式是可以不指定长度,但是要初始化数组。
*/
//int arr[] = {1,2,3,4};
int arr[2];
int arr1[6] = {1,5,7};
int len = sizeof(arr1)/sizeof(int);
}
需要注意的是:在C语言中是不对数组的脚标进行检查的,没有脚标越界的说法;
关于赋值,如果你指定数组的长度是6,而同时也初始化了该数组,那没有被初始化的脚标的值将默认被初始化为0。
要想得到数组的长度,需要计算数组的总长度然后除以数组内元素类型的长度。
下面我们通过例子来看数组的遍历:
#include<stdio.h>
void main(){
//遍历数组
int arr[6] = {1,2,3,7,5,9};
int i = 0;
for(;i<sizeof(arr)/sizeof(int);i++){
printf("输出元素:%d\n",arr[i]);
}
}
结果:
输出元素:1
输出元素:2
输出元素:3
输出元素:7
输出元素:5
输出元素:9
假如我们写成:
#include<stdio.h>
void main(){
//遍历数组
int arr[6] = {1,2,3,7,5,9,7,11};
int i = 0;
for(;i<sizeof(arr)/sizeof(int);i++){
printf("输出元素:%d\n",arr[i]);
}
}
输出结果扔是:
输出元素:1
输出元素:2
输出元素:3
输出元素:7
输出元素:5
输出元素:9
但是编译器会报异常,这种写法,我们声明了6个元素长度的数组,而在初始化的时候我们却写了8个长度,在打印输出的时候我们根据计算是按照左边声明的长度来输出的。
假如我们想通过一个方法来遍历数组:
#include<stdio.h>
void IteratorArray(int* arr,int len){
int i = 0;
for(;i<len;i++){
printf("shuchu:%d\n",arr[i]);
}
}
void main(){
//遍历数组
int arr[8] = {1,2,3,7,5,9,7,11};
IteratorArray(arr,sizeof(arr)/sizeof(int));
}
我们需要注意的是,数组在进行参数传递的时候会变成地址来传递。(也是,不管是变量,函数,数组都有地址,在C中我们可以通过地址来找到它们然后是使用它们)这里我们在调用IteratorArray方法的时候传递的arr参数,arr是作为地址的形式传递给了IteratorArrray方法参数。
好了,大概说完了数组,我们再来插一个话题,说一下C语言在Linux中的内存模型,在Linux中,
C在内存中分为:Text段,Data段和BSS段,Text段存放着二进制代码,相当于java中的字节码文件。Data段又有heap(堆)、stack(栈)、static data(相当于java中的静态池方法区)三块内存区域,这里需要了解的是在Java中我们知道要在堆中开辟一块区域,只要new一个对象即可,而在C中,我们需要调用malloc(memory allocation)来取得内存区域,在java中栈中的区域是编译器自动分配的,static data 用来存放已经初始化的常量和静态变量;BSS(Block start by symbol)用来存放未初始化的常量和静态变量。
接下来我们来讲一下C语言动态申请数组空间,看代码:
#include<stdio.h>//导入头文件
void main(){
/*
传统的方式我们去动态的创建一个数组并遍历它
*/
int len;
printf("请输入您需要的数组长度:\n");
scanf("%d",&len);
printf("数组的长度是:%d\n",len);
int arr[len];
int i = 0;
for(;i <= len;i++){
printf("请输入数组的第%d个元素:",i);
scanf("%d",&arr[i]);//得到键盘输入的内容,给数组去动态的赋值
}
for(i = 0;i <= len;i++){
printf("%d\t",arr[i]);
}
}
输出结果:
请输入您需要的数组长度:
2
数组的长度是:2
请输入数组的第0个元素:1
请输入数组的第1个元素:2
请输入数组的第2个元素:3
1 2 3
该方法是用比较传统的方式来动态创建数组。下面我们来介绍一个方法来动态申请空间来创建数组,代码:
#include<stdio.h>//导入头文件
#include<stdlib.h>//导入标准库文件
void main(){
int len;
printf("请输入数组长度:\n");
scanf("%d",&len);
//根据键盘录入的期望长度来动态申请数组的长度(堆内申请),注意,在C中并没有垃圾回收机制,当数组不用的时候需要手动调用free函数去回收
/*
我们知道 数据类型* 是指该种类型的指针变量,也就是这种指针指向其对应的数据类型
这里的void*并不是指该指针指向类型为void的数值区域,而是类似于java中的object,表示不确定的可以指向任意类型
这里申请的内存仍然返回的是一个指针,我们可以通过指针来访问该区域里的数据
*/
//void* arr = malloc(len * sizeof(int));
/*
这里因为申请的内存空间是int型数据,所以我们要将它强转为int型,否则会报错
*/
int* arr = malloc(len*sizeof(int));
/*
去给数组动态的赋值
*/
int i = 0;
for(;i<=len;i++){
printf("请输入第%d个元素的值:",i);
//用scanf来接收键盘录入的值并给数组各元素赋值
scanf("%d",&arr[i]);
}
for(i = 0;i <= len;i++){
printf("%d\t",arr[i]);
}
//释放指针指向的内存空间
free(arr);
}
输出结果是:
请输入数组长度:
2
请输入第0个元素的值:1
请输入第1个元素的值:2
请输入第2个元素的值:3
1 2 3
上面注释已经写的很详细了,还有注意的是C中没有垃圾回收机制,当数组不用的时候我们要调用free函数将其回收掉,不然内存也会溢出。
二、结构体:
结构体是C中一个非常重要的数据类型,它很类似于java中的类,下面我们通过代码来认识它:
#include<stdio.h>//导入头文件
/*声明结构体:
第一种方式:
*/
struct Student{
int age;
char* name;//char* 指向字符串
float score;
};
/*声明结构体:
第二种方式:
*/
struct Human{
int age;
char* name;
}man;
/*声明结构体:
第三种方式:
*/
struct{
int age;
char* name;
}p;
void main(){
//调用第一种方式
struct Student stu = {18,"小刚",80.5};
printf("信息是:age = %d name = %s score = %f\n",stu.age,stu.name,stu.score);
//调用第二种方式
man.age = 24;
man.name = "小明";
printf("信息是:age = %d name = %s\n",man.age,man.name);
//调用第三种方式
p.age = 27;
p.name = "小朋友";
printf("信息是:age = %d name = %s\n",p.age,p.name);
}
执行结果:
信息是:age = 18 name = 小刚 score = 80.500000
信息是:age = 24 name = 小明
信息是:age = 27 name = 小朋友
我们可以看到创建结构体有三种方式,第一种跟java创建一个类很像,声明一个类名,还有属性;
第二种,我们是直接在声明完了之后直接定义一个“对象”出来,然后在后面的逻辑中可直接赋值或者调用;
第三种,很像是java中的匿名类,我们没有指定它是一个什么类,但是有属性,除此之外我们跟第二种方式一样,可以在后面的代码中引用处理。
在C中,不管是变量,函数还是数组我们都有长度,因为我们都可以通过地址去访问去操作它们,接下来我们也来看看结构体的长度,看代码:
#include<stdio.h>//导入头文件
struct Student{
int age;
char* name;
float score;
};
void main(){
struct Student stu = {18,"小红",90};
//分别打印出定义的结构体的长度
printf("长度是:%d\n",sizeof(stu));
}
结果:长度是:12
我们猜想,int的类型是4字节,char*属于指针类型,统一的也是4字节,float是4个字节,4+4+4=12,没错,长度是12字节。
是不是这样呢,我们再来一个例子:
#include<stdio.h>//导入头文件
struct Student{
int age;
char* name;
float score;
char a;
};
void main(){
struct Student stu = {18,"小红",90};
//分别打印出定义的结构体的长度
printf("长度是:%d\n",sizeof(stu));
}
执行结果,长度是:16。我给结构体Student加了一个char a属性,结果最后竟然是16。不对呀,char是一个字节,按照我们的分析结果应该是12+1=13才对吧。
是这样的,在分配空间的时候,编译器的确会将结构体中的各个属性的长度相加,但是在相加之后,它会看该值是否会被该结构体所有数据类型中最大的那个类型长度整除,如果不能的话,内存会继续分配,知道可以整除为止。也就是说,13没法整除4,内存会继续分配到满足该条件的最小的空间16。
再接下来我们再来进一步看一下结构体的一些基本用法,看代码:
#include<stdio.h>//导入头文件
struct Student{
int age;
char* name;
float score;
void (*function)(char*);
};
void study(char* subject){
printf("学习%s真是有意义!",subject);
}
void main(){
struct Student stu = {18,"小红",90};
//分别打印出定义的结构体的长度
/* printf("长度是:%d\n",sizeof(stu));
stu.function = study;//用函数指针指向函数study
stu.function("C语言");//给函数指针传参数,也就是为其指向的study函数传参 */
struct Student* stut = &stu;//定义一个Student结构体类型的指针,指向类型是Student的变量
(*stut).function = study;
(*stut).function("Java");
(*stut).age = 20;
printf("%d\n",(*stut).age);
}
执行结果:学习Java真是有意义!20
我们看到,可以在结构体中声明一个函数指针,函数指针的用法我们之前已经讲过了,基本就是定义一个类似于接口的东西,它的目的就是去指向某一个函数的地址进而去访问该函数。
三、联合体:
简单说完结构体,我们再来了解一下联合体,我们这里仅简单的了解一下它的声明方式和长度,代码如下:
#include<stdio.h>
//声明联合体
union Size{
int age;//长度是4
char ch;//长度是1
char* name;//长度是4
};
void main(){
//创建一个联合体类型实体 并赋值
union Size s;
s.age = 18;
s.ch = 'a';
s.name = "打好基础,不断学习";
//打印联合体的大小
printf("该联合体的长度是:%d\n",sizeof(s));
}
输出结果:该联合体的长度是:4
上面的代码简单介绍了联合体的创建、赋值、还有打印其长度,这里主要,联合体跟结构体好像有点类似,在空间分配上有点相反,联合体是在声明的类型的属性中挑选一个长度最大的分配空间,其他属性公用这一块空间,也就导致了当你给属性一赋值之后,又给另一个属性赋值,将导致空间里的数据重新填充,前面的内容将变成空,假如你还是回去取前面覆盖掉的数据时,会返回一个随机值,而不是我们当初给定的值。
四、枚举
这篇的最后我们在说一下结构类型中的枚举,枚举在java中我们也是用到过的,这里也是简单的交给大家用和看,哈哈。贴代码:
#include<stdio.h>
//定义枚举
enum Day{
MANDAY,TUESDAY,WENDSDAY,THURSDAY,FRIDAY,STARDAY,SUNDAY
};
void main(){
enum Day today = WENDSDAY;
printf("%d\n",today);
}
打印结果是:2
我们这里需要知道的是,在枚举中的元素,编译器是默认会从0开始编号的,并且编号是连续的,我们打印元素时打印的是这个编号。
枚举类型中的元素可以给它赋值,如:
#include<stdio.h>
//定义枚举
enum Day{
MANDAY,TUESDAY = 102,WENDSDAY,THURSDAY,FRIDAY,STARDAY,SUNDAY
};
void main(){
enum Day today = WENDSDAY;
printf("%d\n",today);
}
打印结果是:103