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

C语言入门完结篇_结构体、枚举、时间函数的、变量类型(C语言划分内存各个区块的方法)、文件操作

0、前言:

  • c语言这个专栏,我写了已经7篇文章了,也算是通过自己学习归纳总结了一条对于自己来说学习c入门的最短路径。
  • 一些小问题的总结:
    • 如果使用了类似string.h的头文件,那么其中的函数,可以写在哪些位置?

      • 答:从标准规则而言,可以写在该文件下的任何位置,但也有特殊;比如调用stdlib.h中的srand函数,一般来说写一次就可以了,一般写在主函数中,或者自定义函数中,因为它只要执行,就是全局范围内修改了随机数种子,不建议单独写在主函数外面。
        #include <stdlib.h>  // 提供 srand() 的声明
        #include <stdio.h>// 1. 写在主函数外(全局作用域,但实际调用仍需在函数内)
        void init_random() {srand(42);  // 合法:在自定义函数内调用
        }int main() {// 2. 写在主函数内srand(100);  // 合法// 3. 调用其他包含 srand() 的函数init_random();return 0;	}
        // 4. 写在主函数外(全局作用域,但直接写函数调用会报错)
        // srand(10);  
        // 非法:函数调用不能在全局作用域(文件顶部)直接执行
        
    • 如果需要定义一个静态空数组,在程序的某个地方,给这个数组赋值,那么该用什么方法?

      • 答:要么用函数比如memcpy、strcpy(限字符串数组),要么就挨个用for循环赋值。
    • 这篇文章要总结结构体、枚举,他们作为一种数据类型,可以写在c程序的哪些位置?

      • 答:结构体类型和枚举类型定义位置是灵活的,可以写在局部作用域、全局作用域、也可以写在主函数里面(写在主函数里面的是匿名结构体或者匿名枚举)。

1、结构体:

  • 结构体定义:结构体(struct) 是一种用户自定义的复合数据类型,用于将不同类型的数据(成员)组合成一个整体,以便更清晰地表示现实世界中的复杂对象或概念。结构体是 C 语言中组织相关数据的强大工具。
  • 结构体作用:将多个变量(可以是不同类型)组合成一个逻辑单元;结构体可以嵌套,用于实现链表、树、图等高级数据结构。
  • 结构体类型重命名:使用了结构体类型重命名就不能用定义的方式定义结构体变量了。定义一个结构体,其中包含学生的学号、姓名、性别、年龄,并初始化2个结构体变量,下面展示几种方法:
// 用于演示结构体定义的代码:
// 方法一:一般方式
#include<stdio.h>
struct student {int id;char name[20];char sex[2];
}s1,s2;
int main() {s1 = (struct student){ 2001,"xiaoHua","W" }; // 如果不加强制类型转换,编译器会把这部分当做数组。s2 = (struct student){ 2002,"changJin","M" };// 测试是否初始化成功printf("s1_name:%s,s2_name:%s\n", s1.name,s2.name); //s1_name:xiaoHua,s2_name:changJinreturn 0;
}// 方法二:结合结构体重命名
#include<stdio.h>
#include<string.h>
typedef struct student {int id;char name[20];
} st;int main() {st s1, s2;s1.id = 2001;s2.id = 2002;char* p1 = "xiaoHua";char* p2 = "changJin";memcpy(s1.name, p1, 8);memcpy(s2.name, p2, 9);// 测试是否初始化成功printf("s1_name:%s,s2_name:%s\n", s1.name, s2.name); //s1_name:xiaoHua,s2_name:changJinreturn 0;
}
  • 结构体相关概念:结构体名、成员、结构体变量、结构体数组、结构体嵌套、结构体指针(->)
    • 结构体名:结构体名就是定义结构体的时候,跟在struct后面的命名,它就是我们定义的结构体类型的名字。

    • 成员:结构体定义时,写在结构体当中的各种成员类型,就表示了结构体中有哪些成员。

    • 结构体变量:你定义好的结构体是拿来用的,只有定义了结构体变量,才能给它们赋值使用这个结构体。
      在这里插入图片描述

    • 结构体数组,有时候需要很多结构体变量的话,就得用结构体数组来保存了。

    • 结构体嵌套:在一个结构体中包含了另外一个结构体

    • 结构体指针:这是访问结构体指针时的特殊访问方式,比如一个结构体指针指向了一个结构体数组,那么访问其中的结构体变量,就得不断解引用,所以对结构体指针而言,用“->”符号就可以直接通过指针名访问对应的结构体变量的成员。下面代码中演示:

#include <stdio.h>
// 练习:定义一个学生结构体,成员有学号,姓名,和生日,生日需要嵌套结构体,
// 嵌套的结构体定义为日期结构体,成员应该包含年、月、日信息。
typedef struct Date
{int year;int month;int day;
}date;
typedef struct Student
{int number;char name[20];date birthday;
}Stu;
int main()
{Stu arr[3];Stu* p = arr;*p = (Stu){ 2025001, "andy", {2000,10,12} };p++;*p = (Stu){ 2025002, "baobao", {2001,11,12} };p++;*p = (Stu){ 2025003, "daxing", {2000,2,12} };printf("学号:%d\n姓名:%s\n生日:%d年%d月%d日\n", p->number, (*p).name, p->birthday.year, p->birthday.month, p->birthday.day);/*学号:2025003姓名:daxing生日:2000年2月12日*/return 0;
}
  • 结构体数组大小获取方式:通过结构体名获取结构体的大小
// 结构体大小size
#include <stdio.h>struct Example1 {char a;      // 1 字节int b;       // 4 字节double c;    // 8 字节
};int main() {printf("Size of Example1: %zu\n", sizeof(struct Example1)); // Size of Example1: 16return 0;
}
  • 结构体大小构造规则:结构体对齐

1、第一个成员在结构体开始的地址处(画图的时候从0地址开始画)
2、其他成员需要对齐到“对齐数”的整数倍的地址处【对齐数 = 编译器默认的对齐数和该成员的大小的较小值(vs中默认是8字节,在linux默认的对齐数是4)】,一般以该成员大小作为对齐数。
3、结构体的总大小为最大对齐数的整数倍
4、如果出现了结构体嵌套,嵌套的结构体成员的首地址在自己的最大对齐数的整数倍的地址处

struct S1
{char name;int age;
};

在这里插入图片描述

struct S1
{double b;char c;int i;
};
struct S2
{char c1;struct S1 s;double b;
};

在这里插入图片描述

  • 结构体+位域:C语言当中允许结构体成员变量只使用几位,使用位域的目的是节省内存空间,一般软件开发很少使用。
struct Time{int moth : 4; // month只占4位int day : 5; // day只占5位
}

2、枚举:

  • 定义:用标识符表示整型常量的集合,用关键字enum来定义,通俗而言,枚举 就是 “给一堆整数取名字”,让代码更清晰、好读。比如,用 0 表示“红色”,1 表示“绿色”,2 表示“蓝色”…… 但直接写 0、1、2 别人看不懂。
  • 作用:相对于而言,#define 是简单的文本替换,没有类型检查,容易出错,而enum会通过编译器检测,能更安全一点。
  • 枚举中常量编号,默认从 0 开始,但可以自定义编号,如果自己修改了其中一位的编号,从这一位开始,后面每一位的编号以当前位为基础自增1刷新。
  • 案例:
#include<stdio.h>
int main() {enum Week {sun=7,feb=1,fir,wek,th,fie,sta};int num = 7;switch (num) {case feb:printf("feb");break;case fir:printf("fir");break;case wek:printf("wek");break;case th:printf("th");break;case fie:printf("fie");break;case sta:printf("sta");break;case sun:printf("sun");break;default:printf("NO");}return 0;
}

3、时间函数:

  • 使用条件,导入time.h头文件
  • 常见的几个时间函数使用方式
#include <stdio.h>
#include <time.h>// 利用以上的内容,定义函数,对时间进行处理,需要的最后的时间格式为:xxxx年xx月xx日 xx:xx:xx
void printTime(struct tm* ptime)
{printf("%d年%d月%d日 %d:%d:%d\n",ptime->tm_year + 1900,ptime->tm_mon + 1,ptime->tm_mday,ptime->tm_hour,ptime->tm_min,ptime->tm_sec);
}
int main()
{// 获取时间戳的函数原型// time_t time(time_t *ptm);// 返回从1900年1月1日到目前的时间秒数time_t second = time(NULL);printf("从1970年1月1日到当前时间的秒数:%zu\n", second);// 将时间转化为字符串// ctime函数可以完成将时间戳转化为字符串// char* ctime(time_t* ptm);char* str = ctime(&second);printf("当前的时间为:%s\n", str);/*将时间戳转化为结构体:localtimestruct tm* localtime(time_t* ptm)根据时间戳返回一个固定格式的结构体,这个结构体的声明:struct tm{int tm_sec;  秒,取值范围[0,59]int tm_min; 分,取值范围[0,59]int tm_hour; 时,取值范围[0,23]int tm_mday; 日,取值范围[1,31]int tm_mon; 月,以1月为基础进行偏移参考的,取值范围是[0,11]int tm_year; 从1900年开始到今年的年数int tm_wday; 星期几,星期日是0,取值范围是[0,6]int tm_yday; 这一年过去的天数,取值范围[0,365]}*/struct tm* resTime = localtime(&second);printTime(resTime);return 0;
}

4、变量类型:

  • 前言:内存中的各个区块,在C语言中,内存主要划分为栈区堆区、全局/静态存储区(又分为初始化数据段和未初始化数据段)、常量区代码区五大区域;
    • 栈区存放局部变量,函数参数等,先进先出原则,由编译器自动分配和释放;
    • 堆区动态分配的内存块,如通过malloc、calloc、realloc分配的变量,由程序员手动分配和释放(通过free函数),若未释放,程序结束时由操作系统回收;
    • 全局/静态存储区:全局变量和静态变量(由static修饰的全局变量或局部变量),由系统自动管理,程序启动时分配,程序结束时释放。特点是未初始化的变量在程序加载时由系统自动初始化为0或NULL。
    • 常量区由系统自动管理,程序启动时分配,程序结束时释放。
    • 代码区由系统自动管理,程序启动时分配,程序结束时释放。
  • C语言中的变量按生命周期可以分为:全局变量和局部变量,全局变量定义在函数外部,作用域是整个源文件,如果用 extern 关键字声明,还可以跨文件访问,程序运行期间一直存在(存储在静态存储区)。局部变量在函数内部或代码块({})内部 定义,函数调用时创建,函数返回时销毁(存储在栈区)。
  • C语言中的变量按存储类分类可以分为:可以划分为静态变量和动态变量,静态存储变量是在程序运行的时候,分配固定的存储空间并且一直存在。动态存储变量就是函数中的变量,当程序指定到函数的时候,为函数中的变量开辟空间, 当函数调用结束的时候,变量的空间被释放,当再次调用函数,重新为变量再次开辟空间。如下所示就是一个静态变量的例子,静态变量定义在函数中,可以一直保留它的值,且只能初始化一次。
#include <stdio.h>void counter() {static int count = 0;  // 静态局部变量,只初始化一次,值会保留count++;printf("调用次数: %d\n", count);
}int main() {counter();  // 输出: 调用次数: 1counter();  // 输出: 调用次数: 2(值保留了!)counter();  // 输出: 调用次数: 3return 0;
}

5、文件操作:

  • 在C语言中,文件 = 磁盘上的数据集合(比如 .txt、.csv、图片、视频等)。
    • 打开笔记本 → 用 fopen() 打开文件。
    • 读写内容 → 用 fprintf()、fscanf()、fgets() 等读写。
    • 保存并合上笔记本 → 用 fclose() 关闭文件。
// 打开文件操作,模式包括:r、w、a、r+、w+、a+
#include <stdio.h>int main() {FILE *fp = fopen("demo.txt", "w");if (fp == NULL) {printf("文件打开失败!\n");return 1;}fprintf(fp, "第一行:Hello\n");fprintf(fp, "第二行:World\n");fclose(fp);printf("数据写入成功!\n");return 0;
}

6、查缺补漏:

  • 静态空数组赋值的方式:使用strcpy、使用memcpy函数、单个for循环、strcpy函数(仅限字符串数组),在下面的实例中,有一种错误,我专门标了五角星,注释了起来,我们在c中定义了字符数组m之后,然后就给它赋值,这种错误很容易出现,这种基础知识的理解错误,就会延伸到后面的知识当中。
#include<stdio.h>
#include<string.h>struct A {int a;char c;char b[5];
};int main() {// 测试改变静态数组的值int arr1[3] = { 1,2,3 };char arr2[4] = "abc";struct A arr3[3];// 用数组名的方式改变数组的值arr1[1] = 4;arr2[1] = 'm';arr3[1] = (struct A){ 3,'c' };//arr2 = "def"; // ★这是一种很重要的错误!很多时候,我们会习惯这样给字符串赋值//arr3[1].b = "defg"; // 这种错误,就是对上面错误的延伸printf("%d\n", arr1[1]);printf("%d\n", arr2[1]);printf("%d\n", arr3[1].a);// 用for循环也可以// 用memcpyint arr4[3] = { 4,5,6 };memcpy(arr1, arr4, 12); // memcpy中的参数:接收数组、拷贝数组、拷贝的字节数printf("arr1[0]:%d,arr1[1]:%d,arr1[2]:%d\n", arr1[0], arr1[1], arr1[2]); // arr1[0]:4,arr1[1]:5,arr1[2]:6char arr5[4] = "def";memcpy(arr2, arr5, 3);printf("arr2[0]:%c,arr2[1]:%c,arr2[2]:%c\n", arr2[0], arr2[1], arr2[2]); // arr2[0]:d,arr2[1]:e,arr2[2]:fchar* a = "abcd";memcpy(arr3[2].b, a, 5);printf("arr3[2].c: %s\n", arr3[2].b); // arr3[2].c: abcdreturn 0;
}

总结:

  • 通过结构体,就能够发现很多复杂的概念都是由基础的概念组成的,对于c语言这种规则性很强的语言而言,复杂概念无非就是写起来复杂,其本质原理和规则无外乎都是基础概念规则而来。
http://www.dtcms.com/a/327690.html

相关文章:

  • MyBatis 缓存与 Spring 事务相关笔记
  • Spring-Cache 缓存数据
  • damn the jvm again(2)
  • 编程模型设计空间的决策思路
  • 什么是跨域访问问题,如何解决?
  • Windows怎样配置动态磁盘
  • [SC]SystemC中的SC_FORK和SC_JOIN用法详细介绍
  • android端自定义通话通知
  • VUE的8个生命周期
  • Orange的运维学习日记--41.Ansible基础入门
  • sqli-labs通关笔记-第44关 POST字符型堆叠注入(单引号闭合 手工注入+脚本注入3种方法)
  • demo 英雄热度榜 (条件筛选—高亮切换—列表渲染—日期显示)
  • Full GC 频率优化实战
  • RGWRados::get_obj_state_impl()
  • 25C机场航班调度程序(JS 100)
  • 【智能硬件】2025年儿童智能手表革命:守护隐私的科技堡垒
  • AQS的理解
  • B树索引和B+树索引有什么区别?
  • 编译 BusyBox for ARM 平台
  • 数据结构:图
  • 1、正则表达式入门
  • (LeetCode 每日一题) 2787. 将一个数字表示成幂的和的方案数(动态规划dp+01背包)
  • Python 常用的正则表达式
  • CodeRush AI 助手进驻 Visual Studio:AiGen/AiFind 亮相(五)
  • RL推理的尽头,是熵坍缩?统一SFT与强化学习的新视角
  • 零基础学Java第七讲---调试(IDEA)
  • 面试经典150题[001]:合并两个有序数组(LeetCode 88)
  • 【代码随想录day 17】 力扣 98.验证二叉搜索树
  • iis无法访问文件
  • NTP常见日志分析