标准C开发基础
1. C语言概述
1.1 计算机语言介绍
1.1.1 计算机语言分类
计算机语言的主要分为机器语言、汇编语言和高级语言,机器语言和汇编语言是低级语言,直接由硬件执行,而高级语言需要通过编译器或解释器翻译成机器语言才能执行。
1、机器语言
机器语言是以二进制代码表示的指令集合,是计算能直接识别和执行的代码指令。
机器语言的优点是占用内存少、执行速度快。
机器语言缺点是难编写、难阅读、难修改、难移植。
2、汇编语言
汇编语言是将机器语言的二进制代码指令用简单符号(助记符)表示的一种语言。
汇编语言与机器语言本质上是相同的,都可以直接对计算机硬件设备进行操作,具有很高的代码执行效率。
使用汇编语言需要掌握计算机CPU原理,不能跨平台,并且汇编语言在程序编写时容易出现冗长,所以具有较高的出错率。
3、高级语言
高级语言将计算机内部的许多相关机器操作指令,合并后形成的高级程序指令,并且屏蔽了具体操作细节,这样大大简化了程序指令,使编程者不需要了解太多计算机原理就可以进行编程。
目前常见的编程语言(C、Java、Python、C++…)都是属于高级语言。
高级语言代码必须转换为机器语言以后才能被计算机识别和执行,按转换过程又可以分为编译型语言和解释型语言。
1.1.2 编译型语言和解释型语言
1、编译型高级语言:通过编译器将源代码编译成机器语言,然后由计算机执行。编译型语言通常比解释型语言更高效,因为编译器可以将代码优化成机器语言,减少内存占用和执行时间。常见的编译型语言包括 C、C++等。
2、解释型高级语言,不需要编译成机器语言,而是直接解释执行源代码。=。解释型语言的优点是实时性、灵活性强,缺点是代码执行效率略较低,常见的解释型语言包括 Python、JavaScript、Shell等。
1.2 C语言发展历史和主要特点
1.2.1 C语言的发展历史
比C语言更早的B 语言是在 1970 年由贝尔实验室的 Richard B. Cook 和 William A. Shaw 开发的,它是为 UNIX 系统而设计的。
C 语言则在 1973 年由 Dennis Ritchie 在贝尔实验室开发,并于同年引入类型和结构化控制。1978 年,Knuth 和 Ritchie 合著的《The C Programming Language》出版,该书成为C 语言发展的里程碑,标志着 C 语言开始走向世界。
1、1989 年,ANSI 美国标准委员会发布了 C 语言的首个标准,即 C89 标准。
2、1990 年,对C89标准进行了修订,形成了 C90 标准。
3、1999 年,ISO 国际标准化组织发布了 C 语言的第三个标准,即 C99标准。
1.2.2 C语言主要特点
C 语言是一种非常强大和灵活的高级编程语言,适用于需要高效、灵活、可移植和底层的系统开发,例如操作系统、嵌入式系统、游戏开发等。
1、高效性:C语言代码执行速度非常快,因为它是一种编译型语言,将源代码编译成机器码后运行。
2、灵活性:C 语言允许程序员对内存进行直接操作,因此在编写底层系统和嵌入式系统时非常有用。
3、可移植性:C 语言代码可以在多个平台上编写和编译,因此它是一种跨平台的编程语言。
4、强大的控制结构:C语言具有强大的控制结构,例如条件语句、循环语句和函数调用等,使得它可以处理各种任务。
5、代码简洁:相对于其他的传统编程语言,C 语言的代码比较简洁。
6、库支持:C语言有大量的标准库和第三方库可供使用,可以大幅缩短开发时间。
7、内存管理:C语言程序员需要手动管理内存,这也使得它对于内存敏感的任务非常有用,例如游戏开发和资源受限的嵌入式系统开发。
1.2.3 C语系
C语言是目前90%语言的基础,很多语言都是由C语言发展变化而来,因此形成了C语系。C语言因此也被称为母体语言,常见基于C语系的高级语言有:
C++
Java
Python
C#
1.2.4 C语言的优缺点
1、优点
高效
可移植
功能强大
灵活、限制少
2、缺点
更容易隐藏错误
有时会难以理解
有时会难以修改
1.3 我的第一个C语言程序
1.3.1 C语言的开发周期
从代码编写到运行测试,C语言的完整开发周期如下:
1、编辑:编写源程序,在编辑窗口中编写 C 语言代码,例如使用Vi编写hello.c,编写完成后保存并关闭文件
2、预处理:处理包含的头文件和宏指令,如 #include
3、编译和汇编:检查代码语法,生成二进制的目标文件,如hello.o
4、链接:将多个目标文件和库链接成一个可执行程序,如a.out
5、载入:将可执行文件读入内存,形成进程映像,它包含可执行文件的所有代码、数据和堆栈等
6、运行:处理器从main 函数开始执行进程映像中的代码
1.3.2 “#include”指令
#include 指令用于包含头文件信息,它在预处理过程中会将头文件中的内容和当前文件的内容合并。C语言中可以通过双引号或尖括号两种方式包含头文件:
1、用<>包含的头文件,到标准头文件路径(usr/include)下寻找
2、用""包含的头文件,优先到当前目录下寻找
一般情况下,建议用<>包含标准头文件,用""包含自定义头文件。如果头文件既不在当前目录下,也不在标准头文件路径中,可以通过GCC编译器的“-I”选项告诉编译器到哪里寻找这样的头文件。
1.3.3 C语言程序的注释
在 C 语言中,注释的使用是非常重要的,它可以让读者更好地理解代码的含义和功能,同时也可以帮助程序员快速找到代码中的问题,具体分为多行注释和单行注释两种方式:
1、多行注释:以“/”开始,以“/”结束,中间的部分为注释,没有行数限制,但不能嵌套。
int main() {
/* 这里可以写多行注释,9
没有行数限制
但不能嵌套
*/
printf("Hello, World!");
return 0;
}
2、单行注释:从“//”开始到本行末尾是注释。
int x = 10; // 这里可以写单行注释,用于说明 x 的值是 10
1.3.4 C语言编码规范
规范C编码风格可以使代码更加健壮、可读性更高,同时也能够提高代码的可维护性和可扩展性,并可以减少代码出错率,对于初学者可以先掌握一些最基本的规范:
1、语句尽量分在多行中书写
int main (void) {printf ("hello word"); return 0;} // 不好
int main (void) { //分成多行书写,更清晰
printf ("hello world\n");
return 0;
}
2、适当地使用空格、缩进和空行
空格可以使代码更加清晰
缩进可以增强代码的层次感
空行可以增强代码的逻辑性
3、合理地为标识符命名
匈牙利命名,如CreateWindow
下划线方式,create_window
2 变量和数据类型
2.1 变量的定义
2.1.1 什么是变量
在 C 语言中,变量是在内存中分配的一个区域,用于存储数据的值。变量名用于标识和区分不同变量,并且通过变量名可以访问和修改变量的值。变量有类型和值,类型用于描述变量可以存储的数据类型,而值则是变量存储的数据。
2.1.2 变量的类型
C 语言是一种强类型语言,这意味着每个变量都有具体的类型,并且不同类型的变量在内存中占用的空间大小不同,C语言中变量基本类型如下:
1、char:字符型,占1个字节空间。
2、int:整型数,占4个字节空间。
3、float:单精度浮点数,占4个字节空间。
4、double:双精度浮点数,占8个字节空间。
2.1.3 变量的定义
C语言中定义变量必须显式地声明其类型,基本语法格式如下:
类型 变量名; // 定义变量不进行初始化
类型 变量1, 变量2; // 同时定义多个变量
类型 变量名 = 初值; // 定义同时初始化
定义变量示例:
int a = 10; // 定义变量a,初始化其值为10
int b; // 定义变量b但未初始化,其值不确定
2.1.4 输出变量的值
在C语言的标准库中提供了printf()函数可以方便地输出变量的值,基本的使用方法如下所示:
int x=10;
printf(“%d”, x);
int x=10,y=20;
printf(“%d,%d”,x,y);
其中“%d”表示格式化占位符号,输出时会用变量的值进行替换,不同占位符对应不同的数据类型,如下所示:
int:%d
char:%c
float:%f
double:%lf
字符串:%s
格式化占位符号要与变量定义的类型一致,否则可能会导致输出结果的错误。
2.1.5【案例】定义变量并输出
定义多个不同类型的变量,然后使用printf()函数输出他们的值。
示例:var.c
#include <stdio.h>
int main (void) {
int a = 10;
int b;
int c = 20, d;
printf ("a=%d,b=%d,c=%d,d=%d\n", a, b, c, d);
float e = 1.23;
double f = 4.56;
printf ("e=%f,f=%lf\n", e, f);
return 0;
}
2.1.6 变量名必须是合法标识符
定义变量时,变量的名字不能随意指定,必须满足以下的语法要求:
1、必须以字母或下划线开头
int abc; // ok
int _abc; // ok
int 2abc; // error !
int *abc; // error !
2、只能包含字母、下划线和数字,不能包含其他特殊字符
int a_2; // ok
int a-2; // error !
3、字母的大小写敏感
int a, A; // 两个变量
4、变量的名字不能与关键字冲突
int float; // error !
注:在Vi编辑器中,蓝色字体的标识符一般都是关键字,比如int、void、return等
5、外变量名字最好有意义,能够见名知意
int age;
float salary;
int max;
int ttt, zyu; // 不建议
2.1.7【案例】变量的标识符
以下变量的名字中,哪些是正确的,哪些是错误的?
int num;
int 1num; // error
int _num;
int void; // error
int my-name; // error
int num_123;
int NUM;
2.2 C语言数据类型
2.2.1 基本数据类型
C语言中变量的基本数据类型只有char、int、float和double四种,但可以在基本类型前面增加关键字(如unsigned、short、long等)的修饰,改变在内存中占用的空间大小和取值范围。
2.2.2 字符型char
在 C 语言中,字符类型char被视为整数类型,可以表示 ASCII 码表中的字符。在C 标准没有说明普通 char 类型是有符号还是无符号类型(大多数编译器默认char是有符号的),有符号整数类型的取值范围是 -128 到 127,可以通过“unsigned char”表示无符号字符型,取值范围是 0 到 255。
字符型变量的底层存储就是整数,对于字符而言存储的是该字符在ASCII表中的代码
字符常量通过一对单引号(‘’)表示
用printf显示字符的时候,如果用%c显示的是字符,如果用%d显示的就是ASCII码
示例:char.c
#include <stdio.h>
int main() {
char c = 'A';
printf("The value of c is %c\n", c);
printf("The value of c is %d\n", c);
return 0;
}
2.2.3【案例】字符型char的定义和使用
用单引号和整数分别定义char类型变量,进行简单运算后打印。
#include <stdio.h>
int main()
{
char c = 'a';
printf("%c\n", c);
printf("%d\n", c);
c = c + 1;
printf("%c\n", c);
printf("%d\n", c);
c = 65;
printf("%c\n", c);
return 0;
}
2.2.4 整型int
在 C 语言中,整数类型是一个非常重要的概念,用于表示整数类型的数据。整数类型可以分为有符号和无符号类型,其中 signed 表示有符号类型(默认),unsigned 表示无符号类型。
在 16 位机和 32 位机上,int 类型的表示范围不同,这是由于硬件平台的不同导致的。通常情况下,16 位机的 int 类型2字节,而 32 位机的 int 类型最为4字节。另外在 C99 标准中,增加了 long long int 和 unsigned long long int 两种 64 位整数类型。这些类型的大小和取值范围是根据编译器的不同而不同的,因此需要在编译时指定正确的类型。
整数有二进制、八进制、十进制和十六进制四种,在 C 语言中,整数的进制默认为十进制,但在Linux系统编程和嵌入式系统中,二进制和十六进制也是经常使用的,使用printf()函数可以指定格式化符号“%x”输出十六进制数字。
示例:int.c
#include <stdio.h>
int main()
{
int num = 100;
printf("%d\n", num); // 十进制
printf("%x\n", num); // 十六进制
return 0;
}
2.2.5 整型常数
在 C 语言中,整型常数可以分为有符号整型和无符号整型两种类型,常见的表示方式如下:
100:默认为int,十进制
100L:long
100LL:long long
100u:unsigned int
100UL:unsigned long
0100:八进制,64
0x100:十六进制,256
2.2.6 浮点型float和double
浮点数就是数学中的小数,C 标准并没有明确规定浮点数精度的范围。实际上,浮点数在计算机内部通常是用二进制数表示的,而不是精确的数值。因此,浮点数类型在计算机内部存储的都是近似值,而不是精确值,在C语言中提供三种浮点类型:
float:单精度,如1.23f
double:双精度, 如1.23
long double :扩展双精度,如1.23L
示例:double.c
#include <stdio.h>
int main() {
float x = 3.14159; // 定义一个浮点型变量 x,值为 3.14159
printf("x = %f\n", x); // 使用 printf 函数输出 x 的值
double pi = 3.1415926535;
printf("pi = %.10lf\n", pi);
return 0;
}
当前Linux环境中,输出浮点数的默认精度是保留6位小数,可以通过格式化字符串修改精度,比如“%.10lf”表示保留10位小数,“%.2f”表示保留2位小数。
2.2.7 获取变量的内存大小
C语言变量在定义时就已经确定了所占内存的大小,对于系统开发工程师必须清楚地知道每个变量所占内存空间,如果某些复杂类型(比如复合数据类型)无法确定大小时,可以使用sizeof关键字获取。
sizeof看起来像一个函数,但其实是一个运算符,使用格式如下:
sizeof(类型)
sizeof(变量名)
sizeof(表达式)
注:sizeof只关心类型,只会分析括号中的类型的大小,不会对括号中的表达式进行运算。
示例:sizeof.c
#include <stdio.h>
int main()
{
int i;
short int si;
long int li;
unsigned int ui;
unsigned short int usi;
unsigned long int uli;
float f;
double d;
long double ld;
char c;
unsigned char uc;
printf("int 型所占的字节数为:%ld\n", sizeof(i));
printf("short int 型所占的字节数为:%ld\n", sizeof(si));
printf("long int 型所占的字节数为:%ld\n", sizeof(li));
printf("unsigned int 型所占的字节数为:%ld\n", sizeof(ui));
printf("unsigned short int 型所占的字节数为:%ld\n", sizeof(usi));
printf("unsigned long int 型所占的字节数为:%ld\n", sizeof(uli));
printf("float 型所占的字节数为:%ld\n", sizeof(f));
printf("double 型所占的字节数为:%ld\n", sizeof(d));
printf("long double 型所占的字节数为:%ld\n", sizeof(ld));
printf("char 型所占的字节数为:%ld\n", sizeof(c));
printf("unsigned char 型所占的字节数为:%ld\n", sizeof(uc));
printf("int 型所占的字节数为:%ld\n", sizeof(int));
i = 5;
printf("表达式 i = 10 所占的字节数为:%ld\n", sizeof(i = 10));
printf("i = %d\n", i);
return 0;
}
2.3 变量的输入和输出
2.3.1 输出函数printf
标准输出函数printf()是C语言代码中使用最为频繁的一个函数,常用于打印调试、输出变量的值,在前面案例中我们已经多次使用它打印单个变量的值,另外也可以使用它同时打印多个变量或常量。
示例:printf.c
#include <stdio.h>
int main() {
int a = 10;
float b = 20;
printf("a = %d, b = %f, c = %lf\n", a, b, 3.14);
return 0;
}
2.3.2 输入函数scanf
标准输入函数scanf()的功能和printf()正好相反,可以通过终端获取用户输入的数据并保存到变量中,基本语法格式如下:
scanf(“格式化字符串”,&变量名);
其中格式化字符串和printf()函数相同,但要保证和变量的类型相同,并且注意要在变量名字前面加一个取地址符号“&”。
示例:scanf.c
#include <stdio.h>
int main (void) {
int num = 0;
printf("请输入一个整数:");
scanf("%d", &num); // 注意格式化字符串后面不要再加\n
printf("num=%d\n", num);
return 0;
}
2.3.3 输入缓冲区
scanf 函数的输入缓冲区是用于存储从键盘输入的数据的。在 scanf 函数调用时,数据会先存储在输入缓冲区中,然后 scanf 函数将缓冲区中的数据读入程序的指定变量中。
当输入缓冲区中的数据被读入程序时,输入缓冲区中的内容会被清理,以便为新的输入做好准备。但是如果输入缓冲区中的数据类型和scanf函数的格式化字符串不匹配时,可能无法读走数据。这是因为 scanf函数在读取数据时需要匹配格式化字符串,如果格式字符串和输入缓冲区中的数据不匹配,scanf 函数将无法正确地将数据读入程序中。
因此,在使用 scanf 函数时,需要确保输入缓冲区中的数据类型和格式化字符串相匹配,以确保数据能够正确地读入程序中。如果连续使用scanf实现输入操作,为了避免因为某个输入错误影响到后面的输入操作,可以通过下面两行代码清理输入缓冲区。
scanf("%*[^\n]");
scanf("%*c");
对于 "%[^\n]",它的作用是清除输入缓冲区中所有除换行符 "\n" 之外的字符。这是因为 "\n" 是一个换行符,它会将用户输入的下一个字符覆盖。使用 "%[^\n]" 格式化字符串可以将除了 "\n" 之外的字符都清除掉,以便 scanf 函数能够正确地读取用户输入的数据。
对于 “%*c”,它的作用是清除输入缓冲区中的换行符 “\n”。这是因为 “\n” 是一个换行符,它会将用户输入的下一个字符覆盖。使用 “%*c” 格式化字符串可以将换行符 “\n” 清除掉,以便 scanf 函数能够正确地读取用户输入的数据。
2.3.4【案例】输入和输出
分别定义int、char、float、doubles四个变量,然后使用scanf函数获取用户输入数据并保存到变量中,再使用printf函数将变量值打印出来。
示例:sp.c
#include <stdio.h>
int main (void) {
int a;
scanf ("%d", &a);
scanf("%*[^\n]");
scanf("%*c");
printf ("a=%d\n", a);
char c;
scanf ("%c", &c);
scanf("%*[^\n]");
scanf("%*c");
printf ("c=%c\n", c);
float f;
scanf ("%f", &f);
scanf("%*[^\n]");
scanf("%*c");
printf ("f=%f\n", f);
double d;
scanf ("%lf", &d);
scanf("%*[^\n]");
scanf("%*c");
printf ("d=%lf\n", d);
return 0;
}
3 进制转换
3.1 二进制
3.1.1 什么是二进制
二进制是计算技术中广泛采用的一种数制,因为数字计算机只能识别和处理由‘0’.‘1’符号串组成的代码。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”。当前的计算机系统使用的基本上是二进制系统,数据在计算机中主要是以补码的形式存储的。计算机中的二进制其实是一个非常微小的开关,用“开”来表示1,“关”来表示0。
3.1.2 二进制整数
二进制中只能有0和1,不会出现2,因为逢2就进位。我们来看一下某个二进制的整数:
0101 1111
这个二进制数的最后一位的权重就是1,倒数第二位的权重是2,倒数第三位的权重是4,倒数第四位的权重是8,倒数第n位的权重就是2的n-1次方。
示例:0到15的二进制
0=0000 0000 9=0000 1001
1=0000 0001 10=0000 1010
2=0000 0010 11=0000 1011
3=0000 0011 12=0000 1100
4=0000 0100 13=0000 1101
5=0000 0101 14=0000 1110
6=0000 0110 15=0000 1111
7=0000 0111 ……
8=0000 1000
3.2 不同进制之间的转换
3.2.1 二进制转十进制
1、正数
对于二进制数转换为十进制数的方法,可以使用权重乘积相加的方法,比如:0101 1001,计算方法如下:
1*64+0*32+1*16+1*8+0*4+0*2+1*1 = 89
2、负数
对于有符号整数来说,第一位是符号位,0代表正数,1代表负数。二进制负数需要按位取反后加1,可以得到对应的正数,计算结果加上负号即可,比如:1111 1110计算方法如下:
先按位取反 -> 0000 0001
加1 -> 0000 0010 (补码)
0000 0010 -> 对应正数是2,所以计算结果是 -2
3.2.2 十进制转二进制
1、正数
除以2取余,反向输出余数,除到0为止,比如计算整数89的二进制转换:
89÷2 余数1
44÷2 余数0
22÷2 余数0
11÷2 余数1
5÷2 余数1
2÷2 余数0
1÷2 余数1
反向输出余数可以得到: 0101 1001 (第一位符号位)
2、负数
先将负数对应的正数转换成二进制,然后对二进制数按位取反后加1即可,比如-2的二进制转换:
先将2转成二进制 -> 0000 0010
按位取反 -> 1111 1101
再加1 -> 1111 1110
得到-2的二进制格式为:1111 1110
3.2.3 八进制和十六进制
可以把八进制和十六进制看作是对二进制的一种简写方式,八进制是将二进制 3 位一组的形式进行表示,通常用“0~7”来表示。十六进制是将二进制 4 位一组的形式进行表示,通常用“0~F”来表示。为了与十进制区别,通常在八进制数前加入“0”标记,而十六进制数前面加“0X”标记。
示例:二进制到八进制和十六进制的转换
二进制数 0101 1011
三个一组可以写成 01 011 011,八进制就是:0133
四个一组可以写成 0101 1011,十六进制就是:0X5B
4 总结
1、计算机语言分类
计算机语言主要分为机器语言、汇编语言和高级语言三种
高级语言分为编译器和解释型两种
2、C语言的主要优点
高效:C 语言的代码执行速度非常快,因为它是一种编译型语言,将源代码编译成机器码后运行。
灵活:C 语言允许程序员对内存进行直接操作,因此在编写底层系统和嵌入式系统时非常有用。
可移植:C 语言代码可以在多个平台上编写和编译,因此它是一种跨平台的编程语言。
内存管理:C 语言提供了自动内存管理机制,程序员不需要手动管理内存。
3、变量的基本类型
char:字符型,占1个字节空间。
int:整型数,占4个字节空间。
float:单精度浮点数,占4个字节空间。
double:双精度浮点数,占8个字节空间。
4、常用的三个函数
输出函数printf()
输入函数scanf()
计算大小 sizeof() // 本质是运算符