目录
1. C语言是什么?
3. 编译器的选择
3.1 编译和链接
3.2 编译器的对比
6. main函数
7. printf 和 库函数
8. 关键字介绍
8.1 什么是预编译?
8.2 static 的关键词作用?
8.3 const 的作用
8.4 voliate 的作用
8.5 typedef 的作用
8.6 union 的作用
9. 字符和ASCII编码
10. 字符串和 \0
11. 转义字符
12. 语句和语句分类
1. C语言是什么?
人和计算机是怎么交流的:使用计算机语言
C 语言是众多计算机语言中的一种,当然 C++/Java/Go/Python 等都是计算机语言。
①编译型语言C/C++:
test.c / test.cpp 经过 编译器处理 生成 test.exe(可执行程序——二进制的指令)
经过编译器编译的语言叫做编译型语言
②解释性语言Python
3. 编译器的选择
3.1 编译和链接
C语⾔是⼀⻔编译型计算机语⾔,C语⾔源代码都是⽂本⽂件,⽂本⽂件本⾝⽆法执⾏,必须通过编译器翻译和链接器的链接,⽣成⼆进制的可执⾏⽂件,可执⾏⽂件才能执⾏。
C语⾔代码是放在 .c 为后缀的⽂件中的,要得到最终运⾏的可执⾏程序,中间要经过编译和链接2个过程。
注:
1. 每个源⽂件(.c)单独经过编译器处理⽣成对应的⽬标⽂件(.obj为后缀的⽂件)
2. 多个⽬标⽂件和库⽂件经过链接器处理⽣成对应的可执⾏程序(.exe⽂件)
3.2 编译器的对比
C语⾔是⼀⻔编译型的计算机语⾔,需要依赖编译器将计算机语⾔转换成机器能够执⾏的机器指令。那我们常⻅的C语⾔编译器都有哪些呢?
⽐如:msvc、clang、gcc 就是⼀些常⻅的编译器,当然也有⼀些集成开发环境 如:VS2022、XCode、CodeBlocks、DevC++、Clion 等。
集成开发环境(IDE)⽤于提供程序开发环境的应⽤程序,⼀般包括代码编辑器、 编译器 、 调试器 和 图形用户界⾯ 等⼯具。 集成了代码编写功能、分析功能、编译功能、调试功能等⼀体化的开发软 件服务套。
- VS2022 集成了MSVC(安装报包较⼤⼀些,安装简单,⽆需多余配置,使⽤起来⾮常⽅便)
- XCode 集成了clang(苹果电脑上的开发⼯具)
- CodeBlocks 集成了gcc(这个⼯具⽐较⼩众,需要配置环境,不太推荐)
- DevC++ 集成了gcc(⼩巧,但是⼯具过于简单,对于代码⻛格的养成不好,⼀些竞赛使⽤)
- Clion 是默认使⽤CMake,编译器是可以配置的(⼯具是收费,所以暂时推荐⼤家使⽤)
- VsCode 本质上就是编辑器,但可以安装插件,更适用于前端开发使用
不同的IDE可能用的是不同的编译器,而不同的编译器厂商对应自己的库函数,库函数功能、参数可能都相同,但实现的内部可能就根据不同的编译器而大不相同。
VS2022上运行代码的快捷键(编译+链接+运行代码):Ctrl+f5 或者 Fn+Ctrl+F5
调试代码:F10,进入调试代码,在调试中——窗口——监视——有1、2、3、4可选
6. main函数
每个 C 语⾔程序不管有多少⾏代码,都是从 main 函数开始执⾏的, main 函数是程序的⼊⼝,main 函数也被叫做:主函数。 main 前⾯的 int 表⽰ main 函数执⾏结束的时候返回⼀个整型类型的值。所以在 main 函数的最后写 return 0; 正好前后呼应。返回0表示的是正常返回。
- main函数是程序的⼊⼝
- main函数有且仅有⼀个
- 即使⼀个项⽬中有多个.c⽂件,但是只能有⼀个main函数(因为程序的⼊⼝只能有⼀个)
main函数的多种写法:
//main 函数写法:
//第一种:古老
void main()
{
}
//第二种:错误写法
main()
{
}
//第三种:
int main()
{
return 0;
}
//第四种:
int main(void)
{
return 0;
}
//第五种:
int main(int argc,char *argv[])
{
return 0;
}
main函数是可以有参数的,比如第五种写法,参数是特殊用途的,写了也可以不用。
7. printf 和 库函数
printf、scanf 函数需要包含 stdio.h 这个头文件,这里的 printf、scanf 函数 就是库函数。
那什么是库函数呢?
为了不再重复实现常⻅的代码,让程序员提升开发效率,C语⾔标准规定了⼀组函数,这些函数再由不同的编译器⼚商根据标准进⾏实现,提供给程序员使⽤。这些函数组成了⼀个函数库,被称为标准库,这些函数也被称为库函数。在这个基础上⼀些编译器⼚商可能会额外扩展提供部分函数(这些函数其他编译器不⼀定⽀持)。
⼀个系列的库函数⼀般会声明在同⼀个头⽂件中,所以库函数的使⽤,要包含对应的头⽂件。库函数参考链接:https://cplusplus.com/reference/clibrary/
8. 关键字介绍
- 关键字都有特殊的意义,是保留给C语⾔使⽤的
- 程序员⾃⼰在创建标识符的时候是不能和关键字重复的
- 关键字也是不能⾃⼰创建的。
C语言的32个关键字如下:
1 auto break case char const continue default do double else enum ex2 float for goto if int long register return short signed sizeof3 struct switch typedef union unsigned void volatile while
C语言面试时会问到的关于关键字的问题:
8.1 什么是预编译?
在C语言中,预编译(Preprocessor)是一个独立的编译器阶段,它在实际编译之前对源代码进行一些文本处理。预编译器主要负责执行一系列预处理操作,这些操作包括宏替换、文件包含、条件编译等,最终生成经过预处理的代码,供编译器进一步处理。
预编译器的工作是在实际的编译之前对源代码进行一些文本替换和处理,以便为编译器提供更适合处理的代码。以下是预编译器常用的功能:
①宏替换(Macro Replacement): 预处理器可以替换源代码中的宏定义为相应的表达式或语句。这样可以通过宏来定义一些常用的代码片段,提高代码的复用性。
#define PI 3.14159
float radius = 5.0;
float area = PI * radius * radius;
②文件包含(File Inclusion): 预处理器可以将其他文件的内容包含到当前文件中,使用#include指令。这样可以将代码分成多个文件,提高代码的组织性和可维护性。
#include <stdio.h>
int main()
{
printf("Hello, World!\n");
return 0;
}
③条件编译(Conditional Compilation): 预处理器可以根据条件判断是否包含或排除某段代码,使用#if、#ifdef、#ifndef、#else、#elif、#endif等指令。这使得能够根据编译时的条件选择性地包含或排除代码块。
#define DEBUG 1
#if DEBUG
// Debugging code
#else
// Release code
#endif
④其它预处理指令: 预处理器还支持一些其它的指令,比如#define用于定义宏、#undef用于取消宏定义、#pragma用于向编译器发出特定的指令等。
在实际编译过程中,首先会经过预编译阶段,预处理器会根据指令执行相应的操作,生成经过预处理的代码。然后,编译器会对这个经过预处理的代码进行编译,生成目标代码。最后,链接器将目标代码与库函数等进行链接,生成可执行文件。
8.2 static 的关键词作用?
①在全局变量中的作用:
在全局变量前使用 static,可以限制该变量的作用域仅在当前源文件中。这样,该变量对于其他源文件是不可见的。
// File1.c
static int globalVar = 10;
// File2.c
extern int globalVar; // Error! globalVar is not visible in other files
②在函数中的作用:
在函数中使用 static,可以使该函数仅在声明它的文件中可见,防止其它文件调用该函数。
// File1.c
static void myFunction()
{
// Function implementation
}
// File2.c
extern void myFunction(); // Error! myFunction is not visible in other files
③在局部变量中的作用:
在局部变量前使用 static,可以使该变量的生命周期延长至整个程序运行期间,而不是只在函数调用时存在。该变量在程序启动时初始化,在程序结束时销毁,但它的作用域仍然限定在声明它的函数内。
void myFunction()
{
static int localVar = 0; // Will retain its value between function calls
localVar++;
// Rest of the function
}
④在函数声明中的作用:
在函数声明中使用 static,表示该函数仅在声明它的文件中可见,不会被其他文件引用。
// File1.c
static void myFunction(); // Declaration
// File1.c
void myFunction()
{
// Function implementation
}
// File2.c
extern void myFunction(); // Error! myFunction is not visible in other files
总体来说,static 关键字的作用是限定标识符(变量或函数)的作用域或生命周期,具体效果取决于它的使用上下文。
8.3 const 的作用
在 C 语言中,const 是一个关键字,用于声明常量。常量是指一旦赋值后就不能再改变的变量。const 关键字可以用于不同的上下文,具有不同的作用:
①常量变量声明:
使用 const 关键字声明的变量是常量,其值不能在程序执行期间被修改。
const int MAX_VALUE = 100;
在这个例子中,MAX_VALUE 被声明为一个整数常量,一旦赋值就不能再被修改。如果试图修改它的值,编译器将报错。
②指针和 const:
const 还可以用于指针,表示指针指向的值是常量,不能通过该指针修改对应的变量。
int variable = 10;
const int* ptr = &variable;
在这个例子中,ptr 是一个指向整数常量的指针,这意味着通过 ptr 不能修改 variable 的值。
③const 修饰函数参数:
在函数参数列表中使用 const,表示该参数是只读的,函数不能修改这个参数的值。
void printMessage(const char* message) {
// Function body
}
在这个例子中,message 是一个指向字符常量的指针,函数 printMessage 不能修改 message 指向的字符串。
④const 修饰函数返回值:
const 还可以用于函数的返回值,表示函数返回的值是常量。
const int getConstantValue() {
return 42;
}
在这个例子中,getConstantValue 函数返回一个常量值 42。
总体而言,const 的作用是为了增加程序的可读性和可维护性,通过明确地标识出不应该被修改的变量或数据,有助于预防错误并使程序更易于理解。
8.4 voliate 的作用
在 C 语言中,volatile 是一个关键字,用于告诉编译器,被声明为 volatile 的变量可能会被程序以外的因素更改,因此编译器不应该对这个变量进行一些优化操作。volatile 主要用于以下几个方面:
①防止编译器优化: 编译器在编译代码时会进行各种优化,包括对变量的读取和写入进行优化。然而,某些变量可能会在程序的执行过程中被外部因素修改,例如硬件寄存器,中断服务例程等。使用 volatile 关键字告诉编译器不要对这样的变量进行优化,确保每次访问都从内存中读取或写入。
volatile int sensorValue;
②多线程环境中的变量共享: 在多线程环境中,一个线程可能修改一个变量的值,而另一个线程可能读取这个变量的值。为了确保正确的同步,需要使用 volatile 来告诉编译器不要对这个变量进行缓存,而是每次都从内存中读取。
volatile int sharedVariable;
③中断服务例程中的变量: 在嵌入式系统中,中断服务例程(ISR)可能会修改某些变量的值,而这些变量可能被主程序中的代码使用。为了确保正确的同步,需要使用 volatile 来告诉编译器这些变量可能在执行过程中被修改。
volatile int interruptFlag;
总体而言,volatile 关键字用于通知编译器,被声明为 volatile 的变量的值可能会在程序执行过程中被意外地更改,因此编译器不应该进行一些优化,而是应该始终从内存中读取或写入这些变量的值。这对于处理硬件寄存器、多线程环境、中断服务例程等情况非常有用。
8.5 typedef 的作用
typedef 是 C 语言中的一个关键字,它用于为已有的数据类型创建一个新的名字(别名)。typedef 提供了一种在程序中更灵活、更可读的方式来使用数据类型。主要的作用有以下几个:
①为数据类型创建别名:
typedef 可以用来创建一个新的、可自定义的名字,作为某种数据类型的别名。这有助于提高代码的可读性,减少代码中出现的复杂或冗长的数据类型名称。
typedef int Integer; // Integer 是 int 的别名
然后,你就可以使用 Integer 来声明变量,就像使用 int 一样。
Integer myNumber = 42;
②简化复杂的数据类型声明:
typedef 可以用来简化复杂的数据类型声明,特别是对于函数指针、结构体等复杂类型的声明。
typedef void (*FunctionPointer)(int, float); // FunctionPointer 是一个函数指针类型
然后,你可以使用 FunctionPointer 来声明函数指针,而不必在每次声明时写出完整的类型。
FunctionPointer myFuncPtr;
③提高代码的可移植性:
typedef 可以提高代码的可移植性,使代码更容易在不同的系统和编译器之间移植。通过为特定的数据类型创建别名,可以更方便地在不同环境下适应不同的数据类型。
typedef unsigned long long uint64_t; // 使用 typedef 创建 uint64_t 的别名
这样,代码中使用的 uint64_t 就可以在不同系统上被映射到相应的无符号长长整型数据类型。
总体而言,typedef 提供了一种更抽象、更灵活的方式来使用数据类型,使代码更易读、易维护,并提高了可移植性。
8.6 union 的作用
在 C 语言中,union 是一种特殊的数据类型,它允许在同一内存位置存储不同的数据类型。union 的作用主要有以下几个方面:
①节省内存:
union 允许在同一内存位置存储不同类型的数据,但同一时刻只能使用其中的一种类型。这样可以在一定程度上节省内存空间,特别是在一些特殊的数据结构中,例如网络协议的数据包。
union Data
{
int i;
float f;
char str[20];
};
在这个例子中,Data 是一个包含整数、浮点数和字符数组的联合体,但在同一时刻只能使用其中的一种类型,节省了存储空间。
②用于实现类似于多态的效果:
union 可以用于实现一种简单的多态效果,使得一个变量可以存储不同类型的值。
union Value
{
int i;
float f;
char str[20];
};
union Value myValue;
myValue.i = 42;
// Later in the code
printf("%d\n", myValue.i); // Output: 42
在这个例子中,Value 联合体的不同成员可以存储不同类型的值,使得 myValue 可以根据需要存储整数、浮点数或字符数组。
③用于位操作:
union 可以用于进行位操作,特别是在需要访问或修改数据的特定位时。
union BitField
{
struct
{
unsigned int flag1 : 1;
unsigned int flag2 : 1;
unsigned int flag3 : 1;
} bits;
unsigned int value;
};
在这个例子中,BitField 联合体包含一个无符号整数 value 和一个包含多个位字段的结构体 bits。这样可以方便地对特定的位进行读取或修改。
总的来说,union 提供了一种在相同的内存位置存储不同类型数据的方式,它的灵活性使得在一些特殊情况下能够更有效地利用内存。但要注意,使用 union 时需要确保程序的逻辑正确,以免发生未定义的行为。
9. 字符和ASCII编码
ASCII 码表 ,码表含有全部128个ASCII
参考ASCII 码表:https://zh.cppreference.com/w/cpp/language/ascii
我们不需要记住所有的ASCII码表中的数字,使⽤时查看就可以,不过我们最好能掌握⼏组特殊的数据,我们所说的码值 一般说的都是 ASCII 中的十进制:
- 字符A~Z的ASCII码值从65~90
- 字符a~z的ASCII码值从97~122
- 对应的大小写字符(a和A)的ASCII码值的差值是32
- 数字字符0~9的ASCII码值从48~57
- 换行 \n 的ASCII值是:10
- 在这些字符中ASCII码值从0~31 这32个字符是不可打印字符,⽆法打印在屏幕上观察
0 —— 数字 0
'0'—— 字符 0 —— ASCII 码值 是 48
#include <stdio.h>
int main()
{
printf("%c\n", 'Q');
printf("%c\n", 81);//这⾥的81是字符Q的ASCII码值,也是可以正常打印的
return 0;
}
10. 字符串和 \0
C语⾔中如何表⽰字符串呢?使⽤双引号括起来的⼀串字符就被称为字符串,如:"abcdef",就是⼀个字符串。
C语⾔字符串中⼀个特殊的知识,就是在字符串的末尾隐藏放着⼀个 \0 字符,这个 \0 字符是字符串的结束标志。
对于字符串"abcdef",我们实际上看到了6个字符:a,b,c,d,e,f,但是实际上在末尾还隐藏⼀个 \0 的转义字符, \0 是字符串的结束标志。所以我们在使⽤库函数 printf() 打印字符串或者strlen() 计算字符串⻓度的时候,遇到 \0 的时候就⾃动停⽌了。
#include <stdio.h>
int main()
{
char arr1[] = {'a', 'b', 'c'};//arr1数组中存放3个字符
char arr2[] = "abc"; //arr2数组中存放字符串
printf("%s\n", arr1);
printf("%s\n", arr2);
return 0;
}
运行结果:
我们可以看到, arr1 字符数组在打印的时候,打印了 a 、 b 、 c 后还打印了⼀些随机值,这就是因为 arr1 在末尾的地⽅没有 \0 字符作为结束标志,在打印的时候没有停⽌。
但是 arr2 的打印就是完全正常的,就是因为 arr2 数组是使⽤字符串常量初始化的,数组中有 \0 作为技术标志,打印可以正常停⽌。如果在 arr1中最后一个字符添加 \0 ,就可以正常打印。
11. 转义字符
在字符中有⼀组特殊的字符是转义字符,转义字符顾名思义:转变原来的意思的字符。
C语⾔中像这样的转义字符还有⼀些,具体如下:
- \? :在书写连续多个问号时使⽤,防⽌他们被解析成三字⺟词,在新的编译器上没法验证了。
三字母词:在以前的旧编译器,会把 ??)和 ??( 认为是 ] 和 [ ,因此 \?\?) 就不会编译为 ] 但现在编译器不存在三字母词的问题了。
- \' :⽤于表⽰字符常量'
- \" :⽤于表⽰⼀个字符串内部的双引号
- \\ :⽤于表⽰⼀个反斜杠,防⽌它被解释为⼀个转义序列符。
- \a :警报,这会使得终端发出警报声或出现闪烁,或者两者同时发⽣。
- \b :退格键,光标回退⼀个字符,但不删除字符。
- \n :换⾏符。
- \r :回⻋符,光标移到同⼀⾏的开头。
- \t :制表符,光标移到下⼀个⽔平制表位,通常是下⼀个8的倍数,相当于一个Tab键。
- \v :垂直分隔符,光标移到下⼀个垂直制表位,通常是下⼀⾏的同⼀列。
下⾯2种转义字符可以理解为:字符的8进制或者16进制表⽰形式,就是八进制/十六进制的数值前边加一个转义字符 就表示是一个字符。
- \ddd :d d d表⽰1~3个⼋进制的数字。 如: \130 表⽰字符 X
- \xdd :d d表⽰2个⼗六进制数字。 如: \x30 表⽰字符 0
\0 :null 字符,代表没有内容, \0 就是 \ddd 这类转义字符的⼀种,⽤于字符串的结束标志,其
ASCII码值是0。
strlen 函数 和 sizeof 函数的区别:strlen 函数在求字符串长度的时候,统计的是\0之前字符的个数;sizeof是求字符串字节个数,包括 \0。
12. 语句和语句分类
C语⾔的代码是由⼀条⼀条的语句构成的,C语⾔中的语句可为以下五类:
- 空语句
⼀个分号就是⼀条语句,是空语句。
- 表达式语句
表达式语句就是在表达式的后边加上分号。
- 函数调⽤语句
函数调⽤的时候,也会加上分号,就是函数调⽤语句。
- 复合语句
复合语句其实就是前⾯讲过的代码块,成对括号中的代码就构成⼀个代码块,也被称为复合语句。
- 控制语句
控制语句⽤于控制程序的执⾏流程,以实现程序的各种结构⽅式(C语⾔⽀持三种结构:顺序结构、选择结构、循环结构),它们由特定的语句定义符组成,C语⾔有九种控制语句。
可分成以下三类:
1. 条件判断语句也叫分⽀语句:if语句、switch语句;
2. 循环执⾏语句:do while语句、while语句、for语句;
3. 转向语句:break语句、goto语句、continue语句、return语句。