数组:存放相同数据类型
结构体:可以存放多种数据类型
结构体如何定义呢?
struct 结构体标签名{
结构体包含的成员变量或数组;
}结构体变量;
结构体是多种数据类型的集合,它可以包含多个变量和数组,这些数据可以是多种类型
#include
int main() {
struct student
{
char *name;//指针变量
int num;
int age;
char group;
float score;
};//必须要有分号
}
注:结构体是一种程序员可以自己定义的数据类型。
基本数据类型:int float char
构造数据类型:struct
数据类型可以用来定义变量,变量是地址的助记符,我们常常用指针变量,因为指针更加灵活。
struct 结构体名 结构体变量
struct student stu1,stu2;
int a,b;
注:如果后面不需要用结构体名再定义结构体变量,那么就可以不写结构体名;但是结构体名、结构体变量名、结构体成员必须有两个。
一般情况下,结构体的各个成员在内存中是连续存储的。
但是在编译器的具体实现过程中,各个成员之间可能不是连续的,会存在缝隙。
注:C语言内存、C语言内存对齐可以提高寻址效率。
结构题成员变量的获取和赋值:
#include
int main() {
struct s
{
char *name;
int num;
int age;
char group;
float score;
}s1;
s1.name = "ybb";
s1.num = 1912;
s1.age = 18;
s1.group = 'A';
s1.score = 99.5;
printf("%s\n%d\n%d\n%c\n%f\n",s1.name,s1.num,s1.age,s1.group,s1.score);
return 0;
}
结构体成员访问运算符 .
数组成员访问运算符 []
结构体变量名.成员名;
student.name;
结构体成员的赋值可以利用结构成体成员访问运算符再赋值。
#include
int main() {
struct s
{
char *name;
int num;
int age;
char group;
float score;
}s1 = {
"ybb",1912,18,'A',99.5 };
printf("%s\n%d\n%d\n%c\n%f\n",s1.name,s1.num,s1.age,s1.group,s1.score);
return 0;
}
注:在定义时进行整体赋值,在使用过程中只能逐一赋值。
注:结构体是一种自定义的数据类型,作为创建变量的模版,本身不占用内存空间。而结构体变量本身因为包含了实际的数据所以需要内存空间来存储。
结构体数组:本质是一个数组,本来数组只能存储相同数据类型,但是结构体数组因为数组的每个成员是结构体,所以数组的每个元素是可以存放多种数据类型的结构体。
#include
int main() {
struct s
{
char *name;
int num;
int age;
char group;
float score;
}s1[]={
{
"ybb0",1912,18,'A',99.5 } ,{
"ybb1",1912,18,'A',99.5 },{
"ybb2",1912,18,'A',99.5 } };
printf("%s\n%d\n%d\n%c\n%f\n", s1[0].name, s1[0].num, s1[0].age, s1[0].group, s1[0].score);
printf("%s\n%d\n%d\n%c\n%f\n", s1[1].name, s1[1].num, s1[1].age, s1[1].group, s1[1].score);
return 0;
}
示例:
#include
struct {
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在小组
float score; //成绩
}class[] =
{
{
"Li ping", 5, 18, 'C', 145.0},
{
"Zhang ping", 4, 19, 'A', 130.5},
{
"He fang", 1, 18, 'A', 148.5},
{
"Cheng ling", 2, 17, 'F', 139.0},
{
"Wang ming", 3, 17, 'B', 144.5}
};
int main(){
float sum=0;
float avg;
int cal=0;
for (int i = 0; i < 5; i++)
{
sum = sum + class[i].score;
if (class[i].score < 140)
{
cal += 1;
}
}
avg = sum / 5;
printf("%f\n", sum);
printf("%f\n", avg);
printf("%d\n",cal);
return 0;
}
结构体指针的本质是指针,只不过指针指向的位置是结构体。
结构体变量名和数组名不同,数组名在表达式中会被转换成数组指针,而结构体变量名不会。结构体变量名始终代表整个集合本身,去结构体变量的地址必须加&
定义结构体指针:
#include
int main() {
struct stu{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在小组
float score; //成绩
}stu1={
"Li ping", 5, 18, 'C', 145.0},*pstu=&stu1;
struct stu *pstu = &stu1;
注:结构体和结构体变量需要区分
结构体是一种创建数据类型的模版不占内存,结构体变量是一种实实在在的占用内存的数据。
箭头运算符:通过结构体指针直接取得结构体成员变量
利用结构体指针访问结构体成员变量:
#include
int main() {
struct stu {
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在小组
float score; //成绩
}stu1 = {
"Li ping", 5, 18, 'C', 145.0 }, *pstu = &stu1;
printf("%s\n%d\n%d\n%c\n%f\n", (*pstu).name,(*pstu).num,(*pstu).age,(*pstu).group,(*pstu).score);
printf("%s\n%d\n%d\n%c\n%f\n",pstu->name,pstu->num,pstu->age,pstu->group,pstu->score);
return 0;
}
#include
struct stu
{
char *name;
int num;
int age;
char group;
float score;
}c[] = {
{
"ybb1",19125064,24, 'A',99.5555},{
"ybb2",19125064,24,'A',99.5},{
"ybb3",19125064,24,'A',99.5} }, *ps;
int main() {
int A = sizeof(c);
int B = sizeof(struct stu);
printf("%d\n%d\n",A,B);
int len;
len = sizeof(c) / sizeof(struct stu);
printf("%d\n",len);
printf("name\tnum\t\tage\tgroup\tscore\t\n");
for ( ps = c; ps < c+len; ps++)
{
printf("%s\t%d\t%d\t%c\t%f\t\n",ps->name,ps->num,ps->age,ps->group,ps->score);
}
return 0;
}
结构体指针作为函数参数:
结构体变量名代表整个集合本身,作为函数参数时传递的是整个集合,而数组名会被编译器转换成一个指向数组首元素地址的指针。
如果结构体成员较多,会导致一次传递的空间和时间开销交大,影响程序的运行效率,因此采用结构体指针,这是由实参传向形参的知识一个地址。
(ps+i)->name
#include
struct stu
{
char *name;
int num;
int age;
char group;
float score;
}c[] = {
{
"ybb1",19125064,24, 'A',99.5},{
"ybb2",19125064,24,'A',88.5},{
"ybb3",19125064,24,'A',89.5} }, *ps;
void average(struct stu *ps, int len) {
int cal = 0;
float sum = 0;
for (int i = 0; i < len; i++)
{
sum += (ps + i)->score;
if ((ps + i)->score < 90)
{
cal++;
}
}
float avg = sum / 3;
printf("%f\n%f\n%d\n", sum,avg, cal);
}
int main() {
int len = sizeof(c) / sizeof(struct stu);
average(c, len);
return 0;
}
使用define定义很多宏
#include
#define mon 1
#define tue 2
#define wed 3
int main() {
int day;
printf("input number\n");
scanf_s("%d",&day);
switch (day)
{
default:printf("error"); break;
case(mon):printf("monday");
break;
case(tue):printf("tuesday");
break;
case(wed):printf("wednesday");
break;
}
return 0;
}
注:一定要总结数据的规律性,特征分析与提取
C语言提供了enum类型,能够列出所有可能的值,并给他们取一个名字。
枚举类型的定义:
enum 类型名{value,value,value};
枚举类型变量数值需要有规律性。
相关类型变量的定义:
基本数据类型
结构体类型
枚举类型
int a;
struct stu a;
enum week a;
#include
int main() {
enum week
{
mon = 1, tue, wed
};
enum week day;
printf("input number\n");
scanf_s("%d",&day);
switch (day)
{
default:printf("error");
break;
case(mon):printf("monday");
break;
case(tue):printf("tuesday");
break;
case(wed):printf("wednesday");
break;
return 0;
}
注:枚举参数列表的标识符的作用范围在main函数内部,因此不能出现重复定义;
注:枚举参数列表都是常量,不能对他们进行赋值,只能将他们的值赋给其他变量。
注:枚举参数列表不指定则从0开始
枚举与宏定义的编译:
宏定义在预处理阶段将名字B用A替换
枚举在编译阶段将名字B替换成A
因此枚举相当于在编译阶段处理的宏
#include
int main() {
enum week
{
mon = 1, tue, wed
};
enum week day;
printf("input number\n");
scanf_s("%d", &day);
switch (day)
{
default:printf("error");
break;
case(1):printf("monday");
break;
case(2):printf("tuesday");
break;
case(3):printf("wednesday");
break;
return 0;
}
}
注:枚举参数会按照一定的规则替换成对应的数字,所以枚举参数里面那些类似变量名的其实不是变量,他们不占用数据区的内存,而是直接被编译到命令里面,放在代码区,代码区和数据区不一样,数据区可以用地址的形式访问,而放在代码区的枚举参数不能采用&
数据区:常量区、全局数据区、堆区、栈区
枚举类型变量的长度:
#include
int main() {
enum week
{
mon = 1, tue, wed
};
enum week day;
printf("input number\n");
scanf_s("%d", &day);
switch (day)
{
default:printf("error");
break;
case(1):printf("monday");
break;
case(2):printf("tuesday");
break;
case(3):printf("wednesday");
break;
return 0;
}
printf("\n");
printf("%d\n%d\n%d\n",sizeof(enum week),sizeof(day),sizeof(mon));
return 0;
}
10.5C语言共用体union
结构体:
struct 结构体标签名{
参数列表
}结构体变量名;
共用体:
union 共用体标签名{
参数列表
}共用体变量名;
结构体和共用体的辨析:
结构体可以存放不同数据类型的变量,而且各个成员变量会占用不同的内存,彼此之间没有影响;
共用体占用的内存等于最长的成员占用的内存(因为共用体采用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉)
struct data{
char c;
short a;
int b;
double c;
}A,B,C;
注:共用体占用的内存等于最长的成员占用的内存。
#include
union data
{
char ch;
short m;
int n;
};
int main() {
union data a;
printf("%d\n%d\n",sizeof(union data),sizeof(a));
a.ch = 'B';
printf("%X\n%d\n%hX\n",a.ch,a.ch,a.ch);
a.n = 0x23abcdef;
printf("%X\n%d\n%hX\n",a.n,a.n,a.n);
return 0;
}
大端:
数据的低位(后面的)放在内存高地址(上面)
数据的高位(前面的)放在内存低地址(下面)
小端模式:
数据的低位(后面的)放在内存的低地址(下面)
数据的高位(前面的)放在内存的高地址(上面)
注:x86的电脑使用的是小端模式
实例:
#include
union data
{
char ch;
short m;
int n;
};
int main() {
union data a;
printf("%d\n%d\n",sizeof(union data),sizeof(a));
a.n = 0x00000001;
if (a.ch==1)
{
printf("little");
}
else
{
printf("big");
}
return 0;
}
什么是位域:在结构体定义时,我们可以指定某个成员变量占用的二进制的位数。
为什么有位域:因为有些数据在存储时并不需要占用一个完整的字节,一个字节可以提供8位使用,而有些数据只有开关两种状态,因此整个字节提供的数据位可供多个这样的数据使用。
如何使用位域:
#include
int main() {
struct bs
{
unsigned m;
unsigned n : 4;
char ch : 6;
}a = {
0X12345678,3,'>'};
printf("%#X\n%#X\n%c\n",a.m,a.n,a.ch);
return 0;
}
注:C语言标准规定,位域的宽度不能超过对应数据类型的长度,否则会导致位域的大小无线,因为位域类型小于位数会导致无意义。
换句话说,位域就是在成员变量所占用的内存中选取不大于该变量类型长度的一种技术。
注:C语言标准规定,int、 unsigned int、char、unsigned char、enum等都支持位域。
位域的存储:
C语言标准没有规定位域的具体存储方式,因为编译器有不同的实现,但是主要原则是压缩存储空间,进而最大化的利用存储空间。
位域的存储规则:
相邻成员类型相同、位宽之和小于类型的bit位数,那么就会在一个类型所占据的bit位内连续存储。位宽之和大于一个类型所占的bit位时,后面的就从新的存储单元开始。
#include
int main(){
struct bs{
unsigned m: 6;
unsigned n: 12;
unsigned p: 4;
};
printf("%d\n", sizeof(struct bs));
return 0;
}
相邻数据类型不同:VS直接开辟下一个,还要保持内存对齐。GCC会进行压缩存储。
#include
int main() {
struct bs
{
int m : 22;
char ch : 5;
int n : 22;
}a = {
1,'A',2};
int len = sizeof(struct bs);
printf("%d\n",len);
}
既有位域又有非位域:
#include
int main() {
struct bs
{
int m : 22;
char ch ;
int n : 22;
}a = {
1,'A',2};
int len = sizeof(struct bs);
printf("%d\n",len);
}
注;C语言内存对齐以提高寻址效率;
注;地址是字节的编号,而不是bit的编号,因此不占用完整字节的位域使用&无意义。
无名位域:
无名位域有类型名,无变量名。
无名位域用于填充bit或者调整成员变量的位置。
逻辑与:&&
逻辑或:||
逻辑非:!
位与:&
位或:|
位异或:^
左移: <<
右移: >>
取反:~
什么是位运算:对bit位进行操作
负数:正数按位取反再加1
注:整数在内存的存储、小数在内存的存储
注:正数在内存的存储、负数在内存的存储
注:位与是根据数据在内存中的二进制位进行运算的。
位与的作用:
将高位或者低位清0
高16位清零0X0000FFFF
低16位清零0XFFFF0000
#include
int main() {
int n = 0X12348765;
int m = n & 0XFFFF;
printf("%#X\n",m);
return 0;
}
#include
int main() {
int n = 0X12348765;
int m = n & 0XFFFF0000;
printf("%#X\n",m);
return 0;
}
按位或的作用:
将某些位 置1
#include
int main() {
int n = 0X12348765;
int m = n | 0XFFFF0000;
printf("%#X\n",m);
return 0;
}
#include
int main() {
int n = 0X12348765;
int m = n | 0X0000FFFF;
printf("%#X\n",m);
return 0;
}
按位异或的作用:
反转某些二进制位
#include
int main() {
int n = 0X0000000C;
int m = n^0X0000000F;
printf("%#X\n",m);
return 0;
}
按位取反运算符~的作用:
#include
int main() {
int n = 0X0000000C;
int m = ~0X0000000C;
printf("%#X\n",m);
return 0;
}
注:8取反为-9
9取反为-10
左移运算符的作用:
左移n位,乘以2的n次方
注:高位丢弃、低位补0
右移运算符的作用:
对于正数:低位丢弃高位补0
对于负数:低位丢弃高位补1
10.9位运算的应用
1是1 0是0 10是10
异或:相异才为1
1与0异或是1 0与0异或是0 相同异或为全0
位异或应用于对称加密
#include
int main() {
char pt = 'a';
char sk = '!';
char st = pt^sk;
char dst = st^sk;
char buffer[100];
printf("%c\n%c\n",st,dst);
printf("name\t\tchar\t\tASCII\t\t\n");
printf("pt\t\t%c\t\t%8s\t\t\n",pt,itoa(pt,buffer,2));
printf("sk\t\t%c\t\t%8s\t\t\n", sk, itoa(sk, buffer, 2));
printf("st\t\t%c\t\t%8s\t\t\n", st, itoa(st, buffer, 2));
printf("dst\t\t%c\t\t%8s\t\t\n", dst, itoa(dst, buffer, 2));
return 0;
}
注:C语言itoa函数和atoi函数
itoa
ltoa
ultoa
itoa实例:
#include
int main() {
int n = 100;
char s[99];
printf("n=%d\ns is:%s\n", n, itoa(n, s, 10));
return 0;
}
#include
int main() {
int n = 100;
char s[99];
printf("n=%d\ns is:%s\n", n, itoa(n, s, 2));
return 0;
}
#include
int main() {
int n = 100;
char s[99];
printf("n=%d\ns is:%s\n", n, itoa(n, s, 20));
return 0;
}