目录
结构体进阶与枚举联合::
结构体进阶:
结构体类型的声明
结构的自引用
结构体变量的定义和初始化
结构体内存对齐
结构体传参
结构体实现位段
枚举:
枚举类型的定义
枚举的优点
枚举的使用
联合:
联合类型的定义
联合的特点
联合大小的计算
C语言编程训练(牛客网)
1.字符串旋转结果
2.BC96-有序序列判断
3.BC106-上三角矩阵判定
4.BC107—矩阵转置
5.BC115-小乐乐与欧几里得
结构体概述:
有时候我们需要将不同类型的数据组合成一个有机的整体,如:一个学生有学号、姓名、性别、年龄、地址等属性.显然单独定义以上变量比较繁琐,数据不便于管理,C语言中给出了另一种构造数据类型——结构体.
结构体的声明:
struct tag
{
member-list;
}variable-list;
//例如:
struct Stu1
{
char name[20];
int age;
char sex[5];
char ID[20];
};
struct Stu2
{
char name[20];
int age;
}s1,s2;
//s1和s2是struct Stu类型的全局变量
结构体的特殊声明:匿名结构体类型
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20],* p;
int main()
{
p = &x; //err 编译器认为匿名结构体是两个完全不同的两个结构体
return 0;
}
//错误写法:
struct Node
{
int date;
struct Node next;
};//这种写法无法计算结点大小:即sizeof(struct Node)无法计算
//正确写法:
struct Node
{
int date;
struct Node* next;
};
//结构体的自引用只能包含结构体成员对应的指针
//错误写法:
typedef struct
{
int data;
Node* next;
}Node;//typedef只能对已然存在的数据类型进行重命名
//不要把匿名结构体类型和自引用相结合
//正确写法:
typedef struct Node
{
int data;
struct Node* next;
}Node;
定义结构体变量的方式:
1.先声明结构体类型,再定义结构体变量.
2.在声明类型的同时定义变量.
3.直接定义结构体类型变量(无变量名).
结构体类型和结构体变量的关系:
结构体类型:指定了一个结构体类型,它相当于一个模型,但其中并无具体数据,系统对之也不分配实际内存单元.
结构体变量:系统根据结构体类型(内部成员状况)为之分配空间.
//结构体嵌套结构体
struct score
{
int n;
char ch;
};
struct Stu
{
char name[20];
int age;
struct score s;
};
int main()
{
struct Stu s1 = { "张三",20,{100,'q'} };
printf("%s %d %d %c\n", s1.name, s1.age, s1.s.n, s1.s.ch);
return 0;
}
结构体成员的使用:
#include
#include
//结构体类型的定义
struct stu
{
char name[50];
int age;
};
int main()
{
struct stu s1;
//如果是普通变量,通过点运算符操作结构体成员
strcpy(s1.name, "lihua");
s1.age = 18;
printf("s1.name = %s s1.age = %d\n", s1.name, s1.age);
//如果是指针变量,通过->操作结构体成员
strcpy((&s1)->name, "hongfei");
(&s1)->age = 22;
printf("(&s1)->name = %s (&s1)->age = %d\n", (&s1)->name, (&s1)->age);
return 0;
}
结构体数组:
//统计学生成绩
struct stu
{
int num;
char name[20];
char sex[10];
float score;
};
int main()
{
//定义一个含有5个元素的结构体数组并将其初始化
struct stu element[5] =
{
{ 101, "Li ping", "男", 45},
{ 102, "Zhang ping", "男", 62.5 },
{ 103, "He fang", "女", 92.5},
{ 104, "Cheng ling", "女", 87},
{ 105, "Wang ming", "男", 58 }
};
int i = 0;
int count = 0;
float average, sum = 0.0f;
for (i = 0; i < 5; i++)
{
sum += element[i].score; //计算总分
if (element[i].score < 60)
{
count++; //统计不及格人的分数
}
}
printf("sum = %f\n", sum);//打印总分数
average = sum / 5; //计算平均分数
printf("平均成绩:average = %f\n", average); //打印平均分
printf("不及格人数:count = %d\n", count);//打印不及格人数
for (i = 0; i < 5; i++)
{
printf(" name=%s score=%f\n", element[i].name, element[i].score);
}
return 0;
}
结构体嵌套结构体:
struct person
{
char name[20];
char sex[10];
};
struct stu
{
int ID;
struct person info;
};
int main()
{
struct stu s[2] = { 1,{"zhangming","男"},2,{"lili","女"} };
int i = 0;
for (i = 0; i < 2; i++)
{
printf("ID = %d info.name = %s info.sex = %s\n",
s[i].ID, s[i].info.name, s[i].info.sex);
}
return 0;
}
堆区结构体变量
//结构体类型的定义
struct stu
{
char name[50];
int age;
};
int main()
{
struct stu* p = (struct stu*)malloc(sizeof(struct stu));
if (p == NULL)
{
perror("malloc fail");
exit(-1);
}
strcpy(p->name, "lihua");
p->age = 22;
printf("p->name = %s p->age = %d\n", p->name, p->age);
printf("(*p).name = %s (*p).age = %d\n", (*p).name, (*p).age);
free(p);
p = NULL;
return 0;
}
结构体嵌套一级指针
//结构体类型的定义
struct stu
{
char* name;
int age;
};
int main()
{
struct stu* p = (struct stu*)malloc(sizeof(struct stu));
if (p == NULL)
{
perror("malloc fail");
exit(-1);
}
p->name = (char*)malloc(strlen("lihua") + 1);
if (p->name == NULL)
{
perror("malloc fail");
exit(-1);
}
strcpy(p->name, "lihua");
p->age = 22;
printf("p->name = %s, p->age = %d\n", p->name, p->age);
printf("(*p).name = %s, (*p).age = %d\n", (*p).name, (*p).age);
free(p->name);
p->name = NULL;
free(p);
p = NULL;
return 0;
}
计算结构体的大小与介绍offsetof:
//计算下列结构体所占字节数
struct S1
{
char c1;
int i;
char c2;
};//12byte
struct S2
{
char c1;
char c2;
int i;
};//8byte
struct S3
{
double d;
char c;
int i;
};//16byte
struct S4
{
char c1;
struct S3 s3;
double d;
};//32byte
结构体内存对齐的规则:
1.第一个成员在与结构体变量偏移量为0的地址处.
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处.
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值.
VS中的默认对齐数为8..
3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍.
4.如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处 ,结构体的总大小就是所
有的最大对齐数(含嵌套结构体的对齐数)的整数倍.
结构体内存对齐存在的原因:
1.平台原因:不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则会抛出硬件异常.
2.性能原因:数据结构(尤其是栈)应该尽可能的在自然边界上对齐,处理器需要两次进行内存访问,而对其的内存只需要进行一次内存访问.
总体来说:结构体的内存对齐是拿空间换取时间的做法.
我们在设计结构体的时候既满足结构体对齐又节省空间的方法:让占用空间小的成员尽量集中到一起.
修改默认对齐数:
#pragma back(4)
struct S1
{
int i;
double d;
};
#pragma back()
int main()
{
printf("%d\n", sizeof(struct S1));
return 0;
}
struct S
{
int data[1000];
int num;
};
void print1(struct S ss)
{
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ", ss.data[i]);
}
printf("%d\n", ss.num);
}
void print2(const struct S* ps)
{
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ", ps->data[i]);
}
printf("%d\n", ps->num);
}
int main()
{
struct S s = { {1,2,3},100 };
print1(s);//传值调用
print2(&s);//传址调用
return 0;
}
位段的定义:
位段的声明和结构体是类似的,不过有两个不同:
1.位段的成员必须是int、unsigned int、signed int、char.
2.位段的成员名后边有一个冒号和一个数字.
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};//8byte
//位段是用来节省空间的
//注:一般情况下位段的成员是同一类型的
位段的内存分配:
1.位段的成员可以是int、unsigned int、signed int 或char类型.
2.位段的空间是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的.
3.位段涉及很多不确定性因素,位段是不跨平台的,注意可移植的程序避免使用位段.
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};//3byte
//char只涉及到一个字节 因此不考虑大小端的问题
int main()
{
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
return 0;
}
1.int位段被当成是有符号数还是无符号数是不确定的.
2.位段中最大位的数目是不确定的(16位机器最大16,32位机器最大32,写成27,在16位机器会出现问题)
3.位段中的成员在内存中从左向右分配还是从右向左分配,标准尚未定义.
4.当一个结构包含两个位段 第二个位段成员比较大,无法容纳第一个位段剩余位时,是舍弃剩余位还是利用是不确定的.
总结:跟结构体相比,位段可以达到同样的效果,但是可以很好的节省空间,但是也存在不跨平台的问题.
位段的应用:
枚举:将变量的值一一列举出来,变量的值只限于列举出来的值的范围内.
enum 枚举名
{
枚举值列表
};
枚举的性质:
在枚举值表中应列出所有可用值,也称为枚举元素.
枚举值是常量,不能在程序中用赋值语句再对它赋值.
枚举元素本身由系统定义了一个表示序号的数值从0开始顺序定义为0,1,2...
enum Day
{
//枚举常量
Mon=1,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
int main()
{
enum Day d = Fri;
printf("%d\n", Mon);
printf("%d\n", Tues);
printf("%d\n", Wed);
return 0;
}
1.增加代码的可读性和维护性.
2.和#define定义的标识符相比,枚举有类型检查,更加严谨.
3.防止了命名污染(封装).
4.与#define相比,便于调试.
5.使用方便,一次可以定义多个常量.
//枚举一般配合switch使用
enum Color
{
red,
blue,
green,
pink,
yellow,
black,
white
};
int main()
{
int value = 0;
scanf("%d", &value);
enum Color color;
switch (value)
{
case red:
printf("红色\n");
break;
case blue:
printf("蓝色\n");
break;
case green:
printf("绿色\n");
break;
case pink:
printf("粉色\n");
break;
case yellow:
printf("黄色\n");
break;
default:
printf("输入错误\n");
break;
}
return 0;
}
union Un
{
int a;
char c;
};
int main()
{
union Un u;
printf("%p\n", &u);
printf("%p\n", &(u.a));
printf("%p\n", &(u.c));
return 0;
}
union Un
{
int a;
char c;
};
int main()
{
union Un u;
u.a = 0x11223344;
u.c = 0x00;
return 0;
}
联合的成员是共用一块空间的,这样一个联合变量的大小,至少是最大成员的大小.
//判断当前机器是大端还是小端
int check_sys()
{
union
{
char c;
int i;
}u;
u.i = 1;
return u.c;
}
int main()
{
//01 00 00 00 -小端
//00 00 00 01 -大端
int ret = check_sys();
if (ret == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
联合的大小至少是最大成员的大小,当最大成员不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍.
#include
//方法1
int IsLeftMove(char arr1[], char arr2[])
{
int len = strlen(arr1);
int i = 0;
for (i = 1; i <= len; i++)
{
int j = 0;
char tmp = arr1[0];
for (j = 0; j < len - 1; j++)
{
arr1[j] = arr1[j + 1];
}
arr1[len - 1] = tmp;
if (strcmp(arr1, arr2) == 0)
{
return 1;
}
}
return 0;
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "cdefab";
int ret = IsLeftMove(arr1, arr2);
if (ret == 1)
{
printf("OK\n");
}
else
{
printf("No\n");
}
return 0;
}
//方法2:使用库函数
int IsLeftMove(char arr1[], char arr2[])
{
int len1 = strlen(arr1);
int len2 = strlen(arr2);
if (len1 != len2)
{
return 0;
}
strncat(arr1, arr1, len1);
char* ret = strstr(arr1, arr2);
if (ret == NULL)
{
return 0;
}
else
{
return 1;
}
}
int main()
{
char arr1[20] = "abcdef";
char arr2[] = "cdefab";
int ret = IsLeftMove(arr1, arr2);
if (ret == 1)
{
printf("OK\n");
}
else
{
printf("No\n");
}
return 0;
}
#include
int main()
{
int n = 0;
scanf("%d", &n);
int arr[50] = { 0 };
int i = 0;
int flag1 = 0;//假设原数组为升序
int flag2 = 0;//假设原数组为降序
for (i = 0; i < n; i++)
{
scanf("%d", &arr[i]);
}
for (i = 0; i < n - 1; i++)
{
if (arr[i] > arr[i + 1])
{
flag1 = 1;
}
else if (arr[i] < arr[i + 1])
{
flag2 = 1;
}
else
{
flag1 = 0;
flag2 = 0;
}
}
if (flag1 + flag2 == 0 || flag1 + flag2 == 1)
{
printf("sorted\n");
}
else
{
printf("unsorted\n");
}
return 0;
}
#include
int main()
{
int n = 0;
scanf("%d", &n);
int arr[10][10] = { 0 };
int i = 0;
int j = 0;
int flag = 1;
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
scanf("%d", &arr[i][j]);
}
}
for (i = 0; i < n; i++)
{
for (j = 0; j < i; j++)
{
if (arr[i][j] != 0)
{
flag = 0;
goto again;
}
}
}
again:
if (flag == 0)
{
printf("NO\n");
}
else
{
printf("YES\n");
}
return 0;
}
#include
int main()
{
int n = 0;
int m = 0;
scanf("%d %d", &n, &m);
int arr[10][10] = { 0 };
int i = 0;
int j = 0;
for (i = 0; i < n; i++)
{
for (j = 0; j < m; j++)
{
scanf("%d", &arr[i][j]);
}
}
for (j = 0; j < m; j++)
{
for (i = 0; i < n; i++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
int main()
{
int n = 0;
int m = 0;
while (scanf("%d %d", &n, &m) == 2)
{
int min = n < m ? n : m;
int max = n > m ? n : m;
int i = min;
int j = max;
while (1)
{
if (n % i == 0 && m % i == 0)
{
break;
}
i--;
}
//i就是最大公约数
while (1)
{
if (j % n == 0 && j % m == 0)
{
break;
}
j++;
}
//j就是最小公倍数
printf("%d\n", i + j);
}
return 0;
}
//方法2:
int main()
{
long n = 0;
long m = 0;
while (scanf("%d %d", &n, &m) == 2)
{
long i = n;
long j = m;
long ret = 0;
while ((ret = i % j) != 0)
{
i = j;
j = ret;
}
printf("%ld", j + (n * m) / j);
}
return 0;
}