Linux和C语言(Day 12)
一、学习内容
-
存储类型
-
定义变量语法格式
-
数据类型 变量名; 存储类型 数据类型 变量名; 【定义变量可以省略存储类型,默认是auto】
-
-
定义函数语法格式
-
数据类型 函数名(参数){} 存储类型 数据类型 函数名(参数){} 【定义函数可以省略存储类型,默认是extern】
-
-
存储类型的作用是什么啊?
-
作用域——能够被使用的范围
-
生命周期——从定义分配内存到释放内存的周期,程序执行阶段
-
默认值——使用不同的存储类型,定义未赋初值的话,默认值不同【0、随机值】
-
-
存储类型auto
-
定义变量未定义存储类型,默认是auto。
-
auto不能修饰全局变量。
-
auto修饰局部变量,()形参、{}
-
默认值是 随机值。
-
-
-
存储类型 register
-
register修饰的是寄存器变量。
-
register修饰的变量 不能进行 &取地址操作。
-
默认值是 随机值
-
CPU从内存取数据,发送给运算器进行数据处理,把结果有存入内存。CPU自己也有内存——寄存器,对于使用频率特别高的数据可以放在寄存器里面,CPU取数据的效率【寄存器 > cache(高速缓存) > 内存】。但是不能不能把所有变量都放在寄存器中,因为寄存器特别小。
-
-
存储类型static【静态】
-
延长生命周期 静态局部变量定义时分配内存,程序结束才释放内存,只赋初值一次。
-
限制作用域 静态全局变量和静态函数只能在该文件内使用,不能被外部文件引用。
-
定义未赋初值,默认值是0
-
-
存储类型extern【引入外部变量/函数】
-
若在1.c文件中定义了一个全局变量,想在2.c文件中使用,使用extern修饰该变量
-
若在1.c文件中定义了一个函数,想在2.c文件中使用,使用extern修饰该函数
-
-
存储类型const【常量】
-
const修饰变量,表示常量化。
-
修饰变量,不可以修改值。 const int a = 666; a = 10; //报错
-
const修饰指针,需要观察 const 与 * 的位置关系,位置不同,作用不同。
-
cont int *p; 或者 int const *p 【可以修改地址、不可以修改值】
-
nt * conts p; 【不可以修改地址、可以修改值】
-
conts int * const p; 【值和地址都不可以修改】
-
-
-
-
-
宏 [不加分号]
-
宏 是预处理命令,用于语法替换。
-
语法格式: #define 宏名 字符串
-
注意:
-
以#开头的属于预处理命令,不是C语言语句,不加分号
-
宏名符合命名规范 最好大写 MAX N MAXSIZE
-
宏只做替换,不做运算
-
宏没有类型检查,所以替换的全是字符串
-
-
自定义宏函数
-
使用({})
-
使用({})定义宏函数,如函数只有一条语句,可以省略({})
-
如有多条语句,返回最后一条语句的结果
-
语法格式
-
#define 宏名(参数) ({语句1; 语句2; 语句3;})
-
注意:宏函数的参数不带数据类型,只写参数名占位即可
-
-
-
使用do...while(0)
-
语法格式
-
#define 宏名(参数) do{语句1;语句2;语句3;}while(0)
-
-
-
-
宏 与 条件编译
-
==============判断宏是否为真====
#if TURE
执行语句
#endif
注释:
#if 0
被注释的语句……
#endif
#define TURE 1
#define FALSE 0 -
========================判断宏已经定义=====
#ifdef 宏名
执行语句
#endif -
=======================判断宏未定义=======
#ifndef 宏名
执行语句
#endif -
======================判断多个宏定义=====
#defined(宏名1) && defined(宏名2)
执行语句
#endif -
=======================判断多个宏未定义=====
#ndefined(宏名1) && ndefined(宏名2)
执行语句
#endif -
======================取消宏====
#undef
宏名
-
-
-
typedef 【重定义数据类型】
-
重定义数据类型
-
typedef int N; //N a; 等价于 int a;
-
typedef int N[3]; //N a; 等价于 int a[3];
-
typedef int *N; //N a; 等价于 int *a;
-
typedef int **N; //N a; 等价于 int **a;
-
typedef int *N[3]; //N a; 等价于 int *a[3];
-
typedef int (*N)[3]; //N a; 等价于 int (*a)[3];
-
typedef int (*N)(); //N a; 等价于 int (*a)();
-
typedef int (*N[3])(); //N a; 等价于 int (*a[3])();
-
-
数据结构中
-
struct student{
int id;
char name[20];
float score;
};
定义学生变量 struct student a;
typedef struct student{
int id;
char name[20];
float score;
}XS;
定义学生变量 XS a;
-
-
-
多文件编程
-
实际的编程中,我们是把所有内容都写在一个.c文件吗
-
不是的。我们会根据内容/数据的不同放在不同的文件中。
-
-
基础学习的过程中: 一般把头文件、宏定义、全局变量、函数声明放在 XXX.h头文件中 把函数实现放在 XXX.c文件中 主函数单独放在 main.c文件中 使用vim -O XXX.h XXX.c main.c 新建三个文件
-
问题
-
一个#include只能包含一个头文件,若需要引入多个头文件,写多个#include
-
一个头文件可以多次引入吗
-
效果和引入一个一样 但是在预处理阶段,都会展开头文件 我们需要防止头文件多次引入 在XXX.h头文件中 #ifndef __HQYJ__ #define __HQYJ__ #include <stdio.h> #include <string.h> 定义全局 函数声明 …… #endif
-
-
#include 包含命令中 "" 和 <> 的区别是什么?
-
"" 先在当前路径下找,找不到去库文件中找 <> 直接去库文件中找
-
-
-
-
面试题总结
-
typedef和define的区别?
-
语法不一样 typedef加; 是C语句 define不加; 不是C语句
-
作用不一样 typedef用于给已有的数据类型重命名 define定义宏
-
define是预处理命令,在预处理直接替换,不做类型检查 typedef在编译时要进行类型检查
-
-
static的作用
-
延长生命周期 静态局部变量定义时分配内存,程序结束才释放内存,只赋初值一次。
-
限制作用域 静态全局变量和静态函数只能在该文件内使用,不能被外部文件引用。
-
定义未赋初值,默认值是0
-
-
const的作用
-
修饰变量,不可以修改值
-
const修饰指针,需要观察 const 与 * 的位置关系,位置不同,作用不同。
-
cont int *p; 或者 int const *p 【可以修改地址、不可以修改值】
-
int * conts p; 【不可以修改地址、可以修改值】
-
conts int * const p; 【值和地址都不可以修改】
-
-
-
-
脑图
二、总结
1. 学习内容概述
多文件编程
学习了如何将程序分成多个文件进行编写和组织,主要包括头文件的创建与引用、源文件的分离以及使用`extern`关键字进行跨文件变量共享。
typedef与结构体
学习了使用`typedef`为复杂的类型定义别名,尤其是在结构体中的应用。`typedef`可以简化代码,增加可读性,常用于定义结构体类型的别名。
宏定义
宏定义是C语言中的一种预处理器指令,通过`#define`定义常量和函数型宏,宏在程序编译前被预处理器替换。了解了宏的优势(如提高代码复用性和简化重复逻辑)以及潜在的陷阱(如宏扩展时的优先级问题)。
字符处理函数
学习了如何使用标准库中的字符处理函数(如`strlen`, `strcpy`, `strcat`, `strcmp`等)进行字符串操作。这些函数提供了常用的字符串处理功能,避免手动实现复杂的字符串操作逻辑。
2. 学习难点
多文件编程的组织
虽然多文件编程可以提升程序的模块化和可维护性,但合理组织多个源文件、确保正确的头文件引用和依赖管理是学习的一个难点。特别是理解`extern`关键字的作用和如何避免重复定义变量。
宏定义的调试
宏并不像函数那样可以进行类型检查,容易导致隐藏的错误。复杂的宏定义在预处理器展开时,可能会因为操作符优先级问题导致意外的行为,调试较为困难。
字符数组与指针的混淆
在字符串操作时,字符数组和字符指针的使用容易混淆。尤其是在处理字符串长度或字符串拷贝时,可能会出现越界或者内存访问错误。
3. 注意事项
头文件的保护机制
在编写头文件时,应该使用“头文件保护”机制,避免重复包含头文件。可以通过`#ifndef`, `#define`, `#endif`来确保头文件只会被编译器加载一次。
宏的使用
在编写宏定义时,应该谨慎处理宏中的操作符优先级问题,可以使用括号明确表达式的计算顺序。同时,避免使用过于复杂的宏,尽量用`inline`函数替代复杂的宏定义。
字符串处理中的边界问题
在处理字符串时,特别是使用`strcpy`, `strcat`等函数时,需确保目标数组有足够的空间存储结果,避免越界操作导致的缓冲区溢出问题。
多文件编程中的依赖管理
在多文件编程时,应该保持代码结构的清晰,合理拆分代码模块,确保每个文件职责单一,避免源文件之间的相互依赖和耦合。
4. 未来学习的重点
Makefile的学习与使用
在多文件编程中,Makefile是管理编译过程的重要工具。未来可以学习如何编写Makefile,自动化管理多个源文件的编译与链接,提升编程效率。
深入研究宏与内联函数的性能对比
宏虽然在一定程度上可以提高代码效率,但由于缺乏类型检查,容易出现问题。可以深入研究宏与`inline`函数的性能和使用场景,选择合适的优化手段。
字符串操作的安全性
未来可以进一步学习如何使用更安全的字符串操作函数,如`strncpy`, `strncat`等,避免因缓冲区溢出带来的安全隐患。
探索动态内存管理与指针
字符串操作涉及大量的内存管理问题,可以深入学习`malloc`, `free`等动态内存管理函数,掌握如何安全高效地分配和释放内存。