C零碎语法
目录
文章目录
- C零碎语法
- 1.编译过程
- 1.2 编译
- 1.3 汇编
- 1.4 链接
- 2.不同位机器,各数据类型所占位数
- 3.assert() 断言(宏)
- 3.1缺点
- 3.2解决办法
- 3.3使用举例
- 3.3.1函数开始处检验传入参数的合法性
- 4.位域
- 4.1举例
- 4.2补充
- 5.typedef/define(取别名)
- 6.输入输出
- 6.1scanf()
- 6.2getchar()
- 6.2.1扩展
- 6.3putchar()
- 6.3.1扩展
- 6.4puts()
- 6.4.1扩展
- 6.5gets()
- 6.5.1扩展
- 7.防止多个文件调用重定义
- 7.1Include Guards
- 7.2Static Functions
- 7.3Anonymous Namespaces (C++ only)
- 8.enum语法
- 8.结构体指针
- 9.指针
1.编译过程
### 1.1 预处理
将所有的#define删除,并且展开所有的宏定义,并且处理所有的条件预编译指令,比如#if #ifdef #elif #else #endif等。
处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。
删除所有注释“//”和“/ /”。
添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
保留所有的#pragma编译器指令,后续编译过程需要使用它们。
$ gcc -E hello.c -o hello.i // 将源文件hello.c文件预处理生成hello.i
// GCC的选项-E使GCC在进行完预处理后即停止
1.2 编译
编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码。
$ gcc -S hello.i -o hello.s // 将预处理生成的hello.i文件编译生成汇编程序hello.s
// GCC的选项-S使GCC在执行完编译后停止,生成汇编程序
1.3 汇编
汇编过程调用对汇编代码进行处理,生成处理器能识别的指令,保存在后缀为.o的目标文件中
当程序由多个源代码文件构成时,每个文件都要先完成汇编工作,生成.o目标文件后,才能进入下一步的链接工作。
$ gcc -c hello.s -o hello.o // 将编译生成的hello.s文件汇编生成目标文件hello.o
// GCC的选项-c使GCC在执行完汇编后停止,生成目标文件
//或者直接调用as进行汇编
$ as -c hello.s -o hello.o //使用Binutils中的as将hello.s文件汇编生成目标文件
1.4 链接
**静态链接是指在编译阶段直接把静态库加入到可执行文件中去,这样可执行文件会比较大。 **
动态链接则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统中把相应动态库加载到内存中去。
2.不同位机器,各数据类型所占位数
3.assert() 断言(宏)
定义在 assert.h 中,其作用是如果它的条件返回错误,则终止程序执行
if(假设成立) {
程序正常运行;
}
else {
报错&&终止程序!(避免由程序运行引起更大的错误)
}
3.1缺点
如果其值为假(即为0),那么它先向 stderr 打印一条出错信息,然后通过调用 abort 来终止程序运行。
使用 assert 的缺点是,频繁的调用会极大的影响程序的性能,增加额外的开销。
3.2解决办法
调试结束后,可以通过在包含 #include 的语句之前插入 #define NDEBUG 来禁用 assert 调用
#include
#define NDEBUG
#include
3.3使用举例
3.3.1函数开始处检验传入参数的合法性
一般只检验一个条件,因为同时检验多个条件时,如果断言失败,无法直观的判断是哪个条件失败
int resetBufferSize(int nNewSize)
{
assert(nNewSize >= 0);
assert(nNewSize <= MAX_BUFFER_SIZE);
...
}
4.位域
有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有 0 和 1 两种状态,用 1 位二进位即可。为了节省存储空间,并使处理简便,C 语言又提供了一种数据结构,称为"位域"或"位段"。
4.1举例
需要一个变量来存储从 0 到 7 的值,您可以定义一个宽度为 3 位的位域
struct
{
unsigned int age : 3;
} Age;
//age 变量将只使用 3 位来存储这个值
struct bs{
int a:8;
int b:2;
int c:6;
}data;
//data 为 bs 变量,共占两个字节。其中位域 a 占 8 位,位域 b 占 2 位,位域 c 占 6 位
4.2补充
一个位域存储在同一个字节中,如一个字节所剩空间不够存放另一位域时,则会从下一单元起存放该位域。也可以有意使某位域从下一单元开始。
struct bs{
unsigned a:4;
unsigned :4; /* 空域 */
unsigned b:4; /* 从下一单元开始存放 */
unsigned c:4
//这个位域定义中,a 占第一字节的 4 位,后 4 位填 0 表示不使用,b 从第二字节开始,占用 4 位,c 占用 4 位。
}
5.typedef/define(取别名)
typedef是类型别名,#define是宏替换。
typedef在编译时处理,#define在预处理时处理。
typedef创建的别名具有类型安全,#define则没有。
typedef可以用于类型声明,而#define通常用于定义常量或宏表达式。
typedef可以用于结构体、联合体、枚举等复杂类型,#define则主要用于简单的文本替换。
typedef unsigned int uint;
#define PI 3.14159
6.输入输出
6.1scanf()
空格、回车也是字符,下面情况也会被读入
scanf("%c%c%c", &a, &b, &c);
输入:a b c
输出:a b
原因:空格被读入,不再作为分隔符
正确输入:abc
6.2getchar()
char c;
c = getchar();
6.2.1扩展
int getchar(void) ==从屏幕读取下一个可用的字符,并把它返回为一个整数。可以在循环内使用这个方法,以便从屏幕上读取多个字符。
6.3putchar()
char c = 'a';
putchar(c);
6.3.1扩展
int putchar(int c) ==函数把字符输出到屏幕上,并返回相同的字符。
6.4puts()
自动加入换行符
printf()支持多种花样输出,而puts()就是输出字符串
puts("请输入一个字符:");
6.4.1扩展
int puts(const char *s) 函数==把字符串 s 和一个尾随的换行符写入到 stdout。
6.5gets()
char str[100];
gets( str);
6.5.1扩展
char *gets(char *s) 函数==从 stdin 读取一行到 s 所指向的缓冲区,直到一个终止符或 EOF。
#include <stdio.h>
int main( )
{
char str[100];
printf( "Enter a value :");
gets( str );
printf( "\nYou entered: ");
puts( str );
return 0;
}
Enter a value :runoob
You entered: runoob
7.防止多个文件调用重定义
7.1Include Guards
#ifndef HEADER_FILE_H
#define HEADER_FILE_H//HEADER_FILE_H是一个唯一的宏,它在头文件第一次被包含时被定义
// 头文件内容
#endif // HEADER_FILE_H
7.2Static Functions
static int myFunction() {
// 函数实现
}
//函数的作用域就被限制在定义它们的文件内,防止在其他文件中被重定义。
7.3Anonymous Namespaces (C++ only)
namespace {
// 定义在匿名命名空间中的实体
void myFunction() {
// 函数实现
}
}
8.enum语法
定义枚举类型的变量,变量的取值将被限制
enum Color {
RED,
GREEN,
BLUE
};
enum Color myColor;
myColor = RED; // 赋值
枚举类型的底层类型默认是int,但你也可以指定其他整数类型
enum Color : unsigned char {
RED,
GREEN,
BLUE
};
8.结构体指针
-
动态内存分配:
使用指针,你可以在堆上动态分配内存来创建结构体实例,这意味着你可以在运行时根据需要分配和释放内存。struct Student *student = malloc(sizeof(struct Student)); if (student != NULL) { // 使用student free(student); // 记得释放内存 }
-
数组和链表:
指针使得创建结构体数组和链表变得简单。你可以使用指针数组来索引结构体,或者创建一个结构体链表来动态地管理数据。struct Student *students = malloc(5 * sizeof(struct Student)); // 分配数组 // 或者 struct Node { struct Student data; struct Node *next; };
-
函数参数传递:
通过指针传递结构体到函数可以避免复制整个结构体,这在结构体很大或者需要修改原结构体时非常有用。void updateStudent(struct Student *student, int newId, float newGpa) { student->id = newId; student->gpa = newGpa; }
-
间接访问:
指针允许间接访问结构体成员,这在你需要通过计算或条件逻辑来确定成员访问路径时非常有用。struct Student *pStudent = &student1; printf("Name: %s\n", (*pStudent).name); // 通过解引用指针访问成员
-
多级指针和指针数组:
可以创建指针的指针,或者指针数组,这在处理复杂的数据结构(如树、图)时非常有用。struct Student **ppStudent = &pStudent; // 指针的指针 struct Student (*ppArray)[10] = malloc(5 * sizeof(struct Student[10])); // 指针数组
-
传递大型结构体:
当结构体较大时,通过指针传递可以减少内存的复制,提高程序的效率。 -
函数返回结构体:
函数可以通过返回指向新分配内存的指针来返回结构体,这允许函数动态地创建并返回结构体实例。struct Student* createStudent(int id, const char* name, float gpa) { struct Student *newStudent = malloc(sizeof(struct Student)); if (newStudent != NULL) { newStudent->id = id; strcpy(newStudent->name, name); newStudent->gpa = gpa; } return newStudent; }
9.指针