C语言学习总结(三) 复杂类型
第五章、复杂数据类型
(数组、字符串、指针、结构体、枚举、共同体)
1.什么是数组?
概念:把具有相同类型的若干变量按有序的形式组织起来,这些按序排列的同类数据元素的集合称为数组;
按数组元素的类型不同,数组又可分为:
- 数值数组:用来存储数值得
- 字符数组:用来存储字符 ‘a’
- 指针数组:用来存放指针(地址)的
- 结构数组:用来存放一个结构体类型的数据
按维度分类:
- 一维数组
- 二维数组
- 多维数组
1.一维数组
概念:所有的元素都不是数组
使用流程:定义数组---->给数组初始化---->使用数组
定义:
数据类型 数组名[长度] ;
//int a[7];
1) 数组的类型实际上是指数组元素的取值类型。对于同一个数组,其所有元素的数据类型都是相同的。
2)第一个数组元素的地址是数组所占内存块的地址。
3) 数组名的书写规则应符合标识符的书写规定,命名不能相同。
4) 方括号中常量表达式表示数组元素的个数,如a[5]表示数组a有5个元素。但是其下标从0开始 计算。因此5个元素分别为a[0], a[1], a[2], a[3], a[4]
5) 不能在方括号中用变量来表示元素的个数,但是可以是符号常数或常量表达式。
6)定义数组不初始化存的是垃圾数,一旦有元素初始化,其他元素都赋值为0;
初始化:
定义:数据类型 数组名[长度]={元素1,元素2,…};
特点:
1)指定元素的个数的同时,对所有的元素进行显式的初始化
int nums[5] = {1,2,3,4,5};
2)指定数组的元素个数,对数组进行部分显式初始化 定义的同时对数组进行初始化,没有显式初始化的元素,那么系统会自动将其初始化为0
int nums[10] = {1,2};
3)不指定元素个数,定义的同时初始化,它是根据大括号中的元素的个数来确定数组的元素个数
int nums[] = {1,2,3,5,6};
4)指定元素个数,同时给指定元素进行初始化
int nums[5] = {[4] = 3,[1] = 2};
5)单个赋值:
数组名[角标]=值;
注意:一个长度为n的数组,最大下标为n-1;超过范围视为越界,会发生未知错误;
引用:
在C语言中只能逐个使用下标变量,而不能一次引用整个数组。
数组名[下标]
数组的遍历:通过循环访问数组的每一个元素。
1 int a[9]={1,2,3,4,5,6,7,8,9};
2 //正序输出
3 for(int i=0;i<9;i++){
4 printf("%d\t",a[i]);
5 }
6
7 //逆序输出
8 for(int i=9;i>0;i--){
9 printf("%d\t",a[i]);
10 }
存储方式:
假设有数组如下:
int x[]={1,2};
char ca[5]={‘a’,‘A’,‘B’,‘C’,‘D’};
注意:
- 计算机会给数组分配一块连续的存储空间
- 数组名代表数组的首地址,从首地址位置,依次存入数组的第1个、第2个....、第n个元素
- 每个元素占用相同的字节数(取决于数组类型)
- 并且数组中元素之间的地址是连续。
注意:字符在内存中是以对应ASCII值的二进制形式存储的,而非上表的形式。
地址:
连续的!
注意:在内存中,内存从大到小进行寻址,为数组分配了存储空间后,数组的元素自然的从上往下排列 存储,整个数组的地址为首元素的地址。
数组的长度计算:
数组的长度=数组的总字节数 / 数组的单个字节数
//count=sizeof(arr[10]) / sizeof(int)
五,有什么特点?
- 1.数组必须要定义长度(正整数),定义时可初始化(长度必须是常量)或之后单独初始化.
- 2.数组是有序的.每个数据都有角标.从0开始分配.角标不能>=数组长度.类型一致.
- 3.数组有地址(数组名),里面的元素也有地址.数组当参数时(长度可省)传递的是地址.
- 4.数组当参数传递时是指针类型.所以如果需要用数组长度要额外传递.
应用:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 使用选择排序实现一组数字的排序--大>>小 2 中心思想:把数组第一个元素的的角标设为最大值,于后面的数字相比较.找出最大值的角标,然后与第一个元素进行调换;找出一个最大值,同理循环 !!选择是把最大的一个数字一到最前面,所以y=x+1..(确定最前面的数字) 3 #include<stdio.h> 4 int main() 5 { 6 int arr[8]={423,4,7,21,89,45,796,34}; 7 int maxzb=0; 8 for (int x=0;x<8; x++) { 9 maxzb=x;//....?? 10 11 for (int y=x+1; y<8;y++) { //y每次的循环初值!!!!! 12 if(arr[maxzb]<arr[y]){ 13 maxzb=y; 14 } 15 } 16 printf("第%d次排序最大角标:%d\n",x,maxzb); 17 int temp=0; 18 temp=arr[x]; 19 arr[x]=arr[maxzb]; 20 arr[maxzb]=temp; 21 for (int s=0; s<8; s++){ 22 23 printf("%d\t",arr[s]);}//输出的是s.不是maxzb!!! 24 } 25 return 0; 26 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 冒泡排序....最大数字移动最后,所以b=0每次从第一个开始.(确定最后一个数) 2 #include<stdio.h> 3 int main() 4 { 5 int arr[]={234,435,7,56,897,2,64,97,42,67}; 6 for (int a=0; a<9; a++) { 7 for (int b=0; b<9-a; b++) { 8 if (arr[b]<arr[b+1]) { 9 int temp=arr[b]; 10 arr[b]=arr[b+1]; 11 arr[b+1]=temp; 12 } 13 } 14 //printf("\n"); 15 } 16 for (int c=0; c<10; c++) { 17 printf("%d\t",arr[c]); 18 } 19 20 return 0; 21 }
数组用作函数参数有两种形式:
- 一种是把数组元素(下标变量)作为实参使用;
- 一种是把数组名作为函数;
1.数组元素就是下标变量,跟普通变量并无区别;
案例:
1 判别一个整数数组中各元素的值,若大于0 则输出该值,若小于 等于0则输出0值
2 void nzp(int v){
3 if(v>0)
4 printf("%d ",v);
5 else
6 printf("%d ",0); }
7 int main( ){
8 int a[5],i;
9 printf("input 5 numbers\n");
10 for(i=0;i<5;i++){
11 scanf("%d",&a[i]);
12 nzp(a[i]);
13 }
14 return 0;
15 }
2.数组名作为函数参数
原理:
当数组名作函数参数时,实参与形参之间不是"值传递",而是"地址传递",所以传递时
实参数组名将该数组的起始地址传递给形参数组,两个数组共享一段内存单元,编译系统不再为形参数组分配存储单元。
案例:
数组a中存放了一个学生5门课程的成绩,求平均成绩。 float avg(float a[],int len){ //定义一个变量用于累加数组中元素的值 float sum=0.0f; //循环累加 for (int i=0; i<len; i++) { sum+=a[i]; } //返回平均成绩 return sum/len; } int main(int argc, const char * argv[]) { //定义数组 float score[]={59.5f,60.0f,88.5f,91.0f,100.0f}; //获得数组长度 int len = sizeof(score)/sizeof(float); //获得平均值 float av = avg(score,len); printf("平均值:%.2f\n",av); return 0; }
注意:
- 形参和相对应的实参都必须是类型相同的数组,否则会引起错误;
- 由于传递的是数组的首地址,所以要把数组长度传递进去;
- 形参可以不给出长度,或用变量表示个数:void nzp(int a[]) / void nzp( int a[], int n )
- 多维数组也可以作为函数的参数。在函数定义时对形参数组可以指定每一维的长度,也可省去 第一维的长度。因此,以下写法都是合法的:int MA(int a[3][10]) / int MA(int a[][10])
2.什么是二位数组?
概念:
是用来存储一组数组的容器,用于把同一类型的一组数组统一管理起来.数组是一种构造类型的数据。
二维数组可以看作是由一维数组的嵌套而构成的。设一维数组的 每个元素都又是一个数组,就组成了二维数组。
当然,前是各元素类型必须相同。根据这样的 分析,一个二维数组也可以分解为多个一维数组。
定义:
类型 数组名[数组的数量][子数组的长度] ;
初始化:
类型 数组名[数组的数量][子数组的长度] :={{数组1}, {数组2},…}
(1)完全初始化
//int b[4]={1,2,3,4};
int a[2][3]={{1,2,3},{4,5,6}}; ------------分段赋值
//赋值的时候可以省略内层的大括号
//系统会自动根据数组的第二维的长度,把值划分为对应的组
int a[2][3]={1,2,3,4,5,6}; ------------连续赋值
//完全初始化,可以省略第一维的长度
int a[][3]={{1,2,3},{4,5,6},{1,2,3}}; //3
(2)部分初始化
只对一部分元素赋值,没赋值的元素默认为0;
赋值:
数组名[数组角标][子数组中数据角标]=值;
int a[m][n];
下标范围:a[0][0] ~ a[m-1][n-1];
a[0][0]=10; //第一个元素
a[m-1][n-1] = 100; //最后一个元素
使用: 数组名[数组角标][子数组中数据角标];
如int a=arr[0][1];
存储方式:
1)计算机会给二维数组分配一块连续的存储空间
2)数组名代表数组的首地址,从首地址位置,依次存入第1行、第2行、.....
3)每一行存储方式,从行首地址还是,依次存储行的第1个元素、第2个元素、第3个元素......
4)每个元素占用相同的字节数(取决于数组类型) 5)并且数组中元素之间的地址是连续。
案例:
1 //一个学习小组有5个人,每个人有三门课的考试成绩。每门课程 的平均分和三门课的平均分。
int main(void){
2
3 int score[5][3]={ {80,75,92},
4 {61,65,71},
5 {59,63,70},
6 {85,87,90},
7 {76,77,85}
8 };
9 int sum=0;
10 float total=0.0f,avg[3];
11 for (int i=0; i<3; i++) {
12 sum=0;
13 for (int j=0; j<5; j++) {
14 sum+=score[j][i]; //计算每一列的值
15 }
16 avg[i]=sum/5.0f;//计算平均分,并且保存到avg数组中
17 printf(" %.2f\n",avg[i]);
18 //将所有的平均分汇总
19 total+=avg[i];
20 }
21 printf("总体的平均分:%f\n",total/3.0f); }
案例:找最大值
int score[5][3]={
{80,75,92},
{61,65,71},
{59,63,70},
{85,87,90},
{76,77,85}
};
int row = sizeof(score)/sizeof(score[0]);
int col = sizeof(score[0])/sizeof(int);
int max=score[0][0];
int maxX=0;
int maxY=0;
for (int i=0; i<row; i++) {
for (int j=0; j<col; j++) {
printf("score[%d][%d]=%d\t",i,j,score[i][j]);
if (score[i][j]>max) {
max=score[i][j];
maxX=i;
maxY=j;
}
}
}
printf("\n\nmax score[%d][%d]=%d\n",maxX,maxY,max);
二维数组做函数参数:
1.二维数组元素作为函数参数,相当于变量值传递;
2.二维数组名作为函数参数:
1)对形参数组定义可以指定维数的大小,也可以省略第一维的大小
如:
void Func(int array[3][10]);
void Func(int array[][10]);
注意:
二者都是合法而且等价,但是不能把第二维或者更高维的大小省略,不合法:void Func(int array[][]);
因为从实参传递来的是数组的起始地址,在内存中按数组排列规则存放(按行存放),而并不区分行和列。
案例:
1 从键盘上接收两个参数分别存放到m,n中,使用m和n构成数组:
2 1)定义一个函数使用i*j 初始化 a[i][j];
3 2)定义一个函数打印二维数组的每一个值
4 #include <stdio.h>
5
6 void print(int x, int y, int a[x][y]){
7 for (int i=0; i<x; i++) {
8 for (int j=0; j<y;j++) {
9 //打印数组的每一个元素
10 printf("a[%d][%d]=%d\t",i,j,a[i][j]);
11 }
12 printf("\n");
13 }
14 }
15
16 void init_array(int x,int y,int a[x][y]){
17 for (int i=0; i<x; i++) {
18 for (int j=0; j<y;j++) {
19 //给a[i][j]赋值i*j;
20 a[i][j]=i*j; }
21 }
22 }
23 int main(int argc, const char * argv[]) {
24 int m,n;
25 int i=m,j=n;
26 //接受数据
27 scanf("%d,%d",&m,&n);
28 //使用m和n构成二维数组
29 int a[m][n];
30 //初始化数组
31 init_array(m,n,a);
32 //打印数组
33 print(m,n,a);
34 printf("\n");
35 return 0;
36 }
2.什么是字符串?
概念:
是一串用双引号字符,也是一个以’\0’结尾的字符数组.
- C语言中没有字符串变量,可以用字符数组来存放字符串
1.字符数组的定义及初始化:
char arr[]=“abc”;
或 char arr[4]={‘a’,’b’,’c’,’\0’}
注意:部分未被使用的元素被自动初始化为0;
输出:
%s 从给定的地址开始,输出字符直到遇到\0结束
printf(“%s\n”,arr); 或 printf(“%s\n”,&arr[0]);
输入:
数组长度为100,因此输入的字符串长度必须小于100,以留出一个字节用于存放字符串结束 标志`\0`。
如果不初始化赋值,必须说明数组长度。输入字符串时不能有空格,因为空格会作为串的结束符;
char ch[10];
scanf("%s",ch);
案例:
1 判断字符串中是否包含某个字符,如果包含,返回其首次出现的 位置,否则返回-1
2
3 int keyIndexOfString(char str[],char key,int len) {
4 for (int i = 0; i < len; i++) {
5 printf("%d\n",i);
6 if (str[i] == key) {
7 return i;
8 }
9 }
10 return -1;
11 }
注意:
- 字符数组和普通数组一样,没有本质区别。
- 数组类型的含义:数据类型指的是数组所包含的元素的类型,而不是数组名的类型,
- 数组名永远是一个指针,指向第一个元素的地址,即数组首地址;
特点:
1.后面必须有’\0’结尾.否则只算普通的字符数组.但’\0’不会输出,只表示字符串结束.
2.字符串输出占位用%s必须遇到\0.才能结束,否则会继续输出更高位地址值的字符.
3.strlen函数用于计算一个字符串的长度(字符数量),使用必须引入<string.h>.
4. strlen不会计算\0.且碰到\0结束,但是sizeof不受\0影响,且长度会包含\0.
5.字符串一定是字符数组,但字符数组不一定是字符串.
补充:字符串处理函数:
0、测字符串长度函数strlen 格式: strlen(字符数组名)
功能:测字符串的实际长度(不含字符串结束标志‘\0’)并作为函数返回值。
1、字符串输出函数 puts
格式: puts(字符数组名)
功能:把字符数组中的字符串输出到显示器。 即在屏幕上显示该字符串。
2、字符串输入函数 gets
格式: gets (字符数组名)
功能:从标准输入设备键盘上输入一个字符串。
3、字符串连接函数 strcat
格式: strcat(字符数组名1,字符数组名2)
功能:把字符数组2中的字符串连接到字符数组1 中字符串的后面,并删去字符串1后的串标志 “\0”。
本函数返回值是字符数组1的首地址。 (字符串1需要足够的长度,否则不能全部装入连接的字符串)
4、字符串拷贝函数strcpy
格式: strcpy(字符数组名1,字符数组名2)
功能:把字符数组2中的字符串拷贝到字符数组1中。串结束标志“\0”也一同拷贝。字符数名2, 也可以是一个字符串常量。
这时相当于把一个字符串赋予一个字符数组。 (字符串1需要足够的长度,否则不能全部装入连接的字符串)
5、字符串比较函数strcmp
格式: strcmp(字符数组名1,字符数组名2)
功能:按照ASCII码顺序比较两个数组中的字符串,并由函数返回值返回比较结果。
字符串1=字符串2,返回值=0;
字符串1>字符串2,返回值>0;
字符串1<字符串2,返回值<0。
本函数也可用于比较两个字符串常量,或比较数组和字符串常量。
案例:
1 使用gets()函数从键盘输入一个字符串,判断输入的有多少个单词?
2 #include <stdio.h>
3 #include <string.h>
4 int main(int argc, const char * argv[]) {
5 char newStr[100]; //接收字符串保存到数组中
6 gets(newStr);
7 int word = 0;
8 int count=0;
9 //for循环遍历
10 for (int i=0; newStr[i]!='\0'; i++) {
11 if (newStr[i]==' ') {
12 word = 0;
13 }else if (word==0){
14 //把单词首字母大写
15 newStr[i] = newStr[i]-32;
16 count++;
17 word = 1;
18 }
19 }
20 printf("单词个数为:%d\n",count);
21 printf("%s\n",newStr);
22 return 0;
23 }
什么是指针?
概念:
指针具有两层含义:一是指一种数据类型.二是指一种保存地址的变量.对于一个内存单元来说,单元的地址即为指针,其中存放的数据才是该单元的内容
用于存储某一个地址,从而可以通过地址直接对该地址内存空间中的数据进行操作.
指针的优点:
- a.为函数供修改调用变量的灵活手段;
- b.让函数有多个返回值
- c.可以改善某些子程序的效率 >>在数据传递时,如果数据块较大(比如说数据缓冲区或比较大的结构),这时就可以使用指针 传递地址而不是实际数据,即高传输速度,又节省大量内存。
- d.为动态数据结构(如二叉树、链表)供支持
定义:
类型 *指针名;
如:int *p;表示定义一个能存int类型地址的指针变量, 名字叫p.
注意:
- 在定义指针时,“*”号表示定义的变量是指针变量,变量的值只能存放地址。
- 一个类型的指针只能指向同类型的变量,不能指向其他类型的变量。
- 指针也可以被声明为全局、静态局部和局部的。
初始化:
1)定义的同时进行初始化
int a = 5;
int *p = &a;
2)先定义后初始化
int a;
int *p;
p=&a;
3)把指针初始化为NULL
int *p=NULL;
int *q=0;
不合法的初始化:
- 1)指针变量不能被赋值一个整数值:int *p; p=100;
- 2)被赋值的指针变量前不能再加“*”说明符,如:*p=&a
注意点:
- 多个指针变量可以指向同一个地址;
- 指针的指向可以改变;
- 指针必须初始化才能访问,否则是野指针错误;
- 定义什么类型的指针就应该指向什么类型的变量。(所有的指针都是占8个字节,取值时候是根据数据类型读取字节数)
取值:使用“*”获取指针对应存储区域的内容
& :取地址运算符;
* :指针运算符(或称“间接访问” 运算符)。
1)在定义变量的时候 * 是一个类型说明符,说明定义的这个变量是一个指针变量
int a=5; int *p=&a; int *p1=p;(P为地址)
2)在不是定义变量的时候 *是一个操作符,访问指针所指向存储空间
int a=5; int *p=&a; int *p1=p;(P为地址)
int x=*p(此处*表示p对应的存储单元的值(间接取值),相当于int x=5)
什么是二级指针?
如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。也称为“二级指针”
特点:
- 1.指针本身也有地址.指针定义之后,自身地址不变,但里面的存的地址可以变化.
- 2. &p表示指针自身地址,而p表示指针存的地址 *p表示指针所存地址的内存空间.
- 3.指针不初始化赋值不要使用.未赋值前地址是随机的.未赋值不可以用*p.
- 4.*修饰什么,就表示是什么的指针.判断原则:先确定变量的类型.(根据符号优先级)
- 5.指针是什么类型的,就应该存什么类型的地址.否则*p取对应内存空间的值会出错.
数组指针
-
什么是指针数组?
表示一个数组,用来存指针的数组就叫指针数组.数组里面的元素都是指针.
如:int *p[3];表示定义一个可以存三个int指针的数组.
-
什么是数组的指针?
表示一个指针,而这个指针类型是数组.
如:int (*p)[3];表示这是一个含有三个元素的int数组的指针.
-
什么是数组元素的指针?
表示一个指针,而这个指针存的是数组一个元素的地址. int arr[1]={1}; int *p=&arr[0];
1.使用指针引用数组元素
引用一个数组元素,可用下面两种方法:
(1)下标法,如a[i]形式
(2)指针法,如*(a+i)或*(p+i)收地
(3)a是常量(a++错误),p是变量(p++正确)
2.指针变量的运算(可以相减,不可相加):
指向同一数组的两个指针变量之间才能进行运算,否则运算毫无意义
两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。
实际上是两个指针值(地址)相减之差再除以该数组元素的长度(字节数)。
3.用数组名访问二维数组
定义:
二维数组名是指向行的,不能被指针变量p直接赋值:
二维数组指针变量说明的一般形式为:
数据类型 (*指针变量名)[二维数组列数];
注意:
1.数组名表示第一个元素的地址,&数组名表示整个数组的地址.二者值相同,但意义不同.
2.指针可以运算.但仅限于加减运算.运算的实质就是地址运算.即地址的移动.
3.地址(+1)向高位移动,而(-1)向低位移动.不同指针类型移动一位(+1或-1)的字节数不同.
4.实际移动的字节数看指针类型,类型占多少个字节,一位就移动多少个字节.
指针与字符串
定义:
一个字符串
char arr[] = ”likui";
指针表示一个字符
char *p = "likui”;
一个字符串数组
char arrs[2][8] = {”likui“, ”yingying“};
指针表示一个字符串数组
char *arrs[2] = {”likui", ”yingying”};
查看字符串的每个元素
特点:
1.指针定义的字符串是常量,不能被改变.而字符数组定义的是变量.可以被改变.
2.指针定义一个字符串,指针存的实际是在常量区的那个字符串的首字符的地址
案例:
1 字符串排序:
2 输入5个国名并按字母顺序排列后输出。
3 char *name[]={ "CHINA","AMERICA","AUSTRALIA","FRANCE","GERMAN"};
4
5 //代码实现:
6 #include <stdio.h>
7 #include <string.h>
8 void print_arr(char *str[],int len){
9 for (int i=0; i<len; i++) {
10 printf("%s\n",str[i]); }
11 }
12 /**
13 * 用冒泡排序法,实现对字符串数组排序 *
14 * @paramname字符串数组
15 * @param len 数组长度
16 */
17
18 void paixu(char *name[],int len){
19 for (int i=0; i<len-1; i++) {
20 for (int j=0; j<len-i-1; j++) {
21 //比较相邻的元素
22 if (strcmp(name[j], name[j+1])>0) {
23 char *temp;
24 temp = name[j];
25 name[j] = name[j+1];
26 name[j+1] = temp;
27 }
28 }
29 }
30 }
31 //选择排序法
32 void sortArray(char *str[],int len){
33 int min;
34 for (int i=0; i<len-1; i++) {
35 min = i;
36 for (int j=i+1; j<len; j++) {
37 if (strcmp(str[j], str[min])<0) {
38 min = j;
39 }
40 }
41 if (i!=min) {
42 char *temp;
43 temp = str[i];
44 str[i] = str[min];
45 str[min] = temp;
46 }
47 }
48 }
49 int main(int argc, const char * argv[]) {
50 char *name[]={ "CHINA","AMERICA","AUSTRALIA","FRANCE","GERMAN"};
51 sortArray(name, 5);
52 print_arr(name, 5);
53 return 0;
54 }
指针与函数
一,什么是指针型函数?
概念:
是函数. 这个函数的返回值类型是指针;
定义:
类型 * 函数名(参数){函数体}
如:int *add(int *a){return &a;}.表示定义一个返回值是int指针类型的add函数.
说明调用函数的时候形参变量是新分配的空间实参和形参只是值传递。
二,什么是函数指针?
概念:
是指针.函数也会在内存开辟一块空间.函数指针即这块空间的地址.
定义:
函数返回值类型 (*指针名)(参数类型);
赋值:指针名=函数名;
如:先定义函数int add(int a){ //...}. int (*p)(int)=add;
使用:指针名(实参); 如:p(3);等效于add(3);
特点:
1.函数指针做加减运算可以,但没有意义.不建议.
2.函数指针可以当作参数传递.因为其是一个指针类型.
3.不管什么类型的指针,只要是指针,其里面存的就是地址.
什么是结构体?
概念:
是一种数据类型.是构造类型.里面可以存不同类型的数据.
用于封装不同的数据类型到一个结构中.方便使用.
定义:
struct 结构体名{
成员列表(类型说明符 成员名)
};
如:定义一个学生的结构
struct stu{
int num;
char name[20]; //char *name;
char sex;
float score;
};
定义结构变量:
struct 结构体名 结构体变量名1,结构体变量名2;
1.如: struct stu boy1 boy2;
2.定义的同时说明结构变量
struct stu{
int num;
char name[20];
char sex;
float score;
}boy1,boy2;
匿名结构体的使用:
struct{
成员表列
}变量名表列;
这种方式省略了结构名,特点是不能再次定义新的结构体变量;
取值:
结构变量名.成员名;
初始化:
结构变量名.成员名=新值;
1.定义的同时初始化
struct stu{
int num;
char name[20];
char sex;
float score;
} av1={1012,"likui",'f',30};
2.先定义结构体变量,然后再初始化
struct stu{
int num;
char name[20];
char sex;
float score;
};
struct stu boy1,boy2;
boy1.num = 1;
boy1.sex = 'f';
boy1.score = 18.0f;
结构体变量存储原理:
结构名只能表示一个结构形式,编译系统并不对它分配内存空间。只有当某变量被说明为这种类型的结构时,才对该变量分配存储空间。
结构体占用的内存空间是每个成员占用的字节数之和(考虑对齐问题)。
对齐:
主要方便计算机的读取,升读取效率
模数:
结构体成员中在内存中占用字节数最大的那个数
分配:
分配模数的倍数
如果有剩余的空间,且能够存储结构体的下一个成员,不再分配新的
否则的都分配一个模数大小的空间
数组,如果类型相同的数组,存在补位。
结构体的作用域:
全局结构体:函数外部定义的;(从定义的那一行开始直到本文件结束为止)
局部结构体:函数内部定义的;(与局部变量相同,用完结束)
注意:
1.可在定义结构体类型时同时定义结构体变量名,可定义匿名类型结构体;
2. 同一作用域内不可重复定义结构体类型和结构变量.匿名类型结构体除外;
3.可在函数内也可在函数外,效果及用法类似于局部变量和全局变量.也可嵌套定义.
4.可同时在函数内外定义同一个类型,使用同样遵循就近原则,结构体变量也是如此.
结构体数组
概念:
是数组,只不是数组里每一个元素是结构体类型,用于存储多个有关联的结构体.
定义:
struct 结构体类型 数组名[长度]={
结构体1,
结构体2
};
1) 定义结构体的同时定义结构体数组
struct Car{
int lunzi;
int speed;
}cars[5];
2) 使用匿名结构体定义数组
struct {
int lunzi;
int speed;
}cars[5];
3)先定义结构体在定义结构体数组
struct Car cars[5];
初始化:
1) 定义结构体数组的同时进行初始化
struct Car{
int lunzi;
int speed;
char name[20];
}cars[2]={{2,50},{80,300}};
2)匿名方式
struct {
int lunzi;
int speed;
}cars[2]={{2,50},{80,300}};
3)先定义数组,后初始化
cars[0].lunzi = 4;
cars[0].speed = 200;
//cars[0].name="xxxx"; 错误的
strcpy(cars[0].name,"xxxx");
const char a;
//用一个整体去初始哈哈
cars[1]=(struct Car){6,100};
特点:
1.遵循数组的特点
结构体的指针
概念:
是指针,指针类型是结构体类型,用于存储结构体的地址,并操作地址对应的结构体空间里的数据
定义及初始化赋值:
struct 结构体类型 *指针名=&结构体变量
如:
struct Person{
char *name;
int age;
};
struct Person per={
”itheima",
2
};
struct Person *p=&per;
取值:
(*指针名).结构体元素名;
指针名->结构体元素名; (结构体独有的用法)
赋值:
(*指针名).结构体元素名=新值;
指针名->结构体元素名=新值
如: (*p).name,(*p).age;或p->name,p->age;
结构体与函数之间的关系:
1)结构体变量的成员变量值做函数参数(除了数组之外,都是值传递)
2)结构体变量作为函数的参数 (值传递)
3)结构体指针作为函数的参数 (地址传递)
特点:
1.指针移动对结构体本身没有意义.结构体名并不包含第一个元素的地址.
枚举
概念:
是一种数据类型.称为枚举类型.是构造类型的一种,里面存的都是有别名的整型常量.可以把多个整型常量以别名的形式封装成一个整体,便于别的变量识别并取值使用.
定义:
1.定义枚举类型 enum 枚举类型{
元素1 ,
元素2 , …};
或{
元素1=0,
元素2=1,
}
2.定义枚举变量及赋值:
enum 枚举类型 变量名=对应枚举里的一个元素名;
取值:变量名=枚举元素名;
什么时候用?
当一个变量只有固定的多个int取值范围时.
特点:
1.枚举类型只能是int值,且不能直接用int常量,需要用别名(建议全部大写)
2.如果不设定值,那么默认第一个元素是0,以此类推;
3.同一个作用域内不能出现重复的元素.不同的枚举类型也不行.