嵌入式C语言基础
本文最后更新于 2024-05-23,文章内容可能已经过时。
什么是C语言
C语言是一门通用计算机编程语言,广泛应用于底层开发。C语言的设计目标是提供一种能以简易
的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。
尽管C语言提供了许多低级处理的功能,但仍然保持着良好跨平台的特性,以一个标准规格写出的
C语言程序可在许多电脑平台上进行编译,甚至包含一些嵌入式理器(单片机或称MCU)以及超级电脑等作业平台。
二十世纪八十年代,为了避免各开发厂商用的C语言语法产生差异,由美国国家标准局为C语言制定了一套完整的美国国家标准语法,称为ANSI C,作为C语言最初的标准。目前2011年12月8日,国际标准化组织(ISO)和国际电工委员会(IEC)发布的C11标准是C语言的第三个官方标准,也是C语言的最新标准,该标准更好的支持了汉字函数名和汉字标识符,一定程度上实现了汉字编程。
C语言是一门面向过程的计算机编程语言,与C++,Java等面向对象的编程语言有所不同。其编译器主要有Clang、GCC、WIN-TC、SUBLIME、MSVC、Turbo C等。
第一个C语言程序
#include <stdio.h>
int main()
{
printf("hello world\n");
printf("Ha Ha\n");
return 0;
}
mian函数是整个程序的入口;
一个工程中main函数有且仅有一个。
数据类型
char //字符数据类型
short //短整型
int //整形
long //长整形
long long //更长的整形
float //单精度浮点数
double //双精度浮点数
//C语言有没有字符串类型
为什么出现这么的类型?
每种类型的大小是多少?
#include <stdio.h>
int main()
{
printf("%d\n", sizeof(char));
printf("%d\n", sizeof(short));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(long));
printf("%d\n", sizeof(long long));
printf("%d\n", sizeof(float));
printf("%d\n", sizeof(double));
printf("%d\n", sizeof(long double));
//sizeof 计算的是变量/类型所占空间的大小,单位是字节
return 0;
/*输出结果:
1
2
3
4
8
4
8
*/
}
一字节=8个比特位大小
注意💡:这么多的类型,是为了更加丰富的表达生活中各种各样的值
类型的使用案例:
char ch = 'w'
int weight = 120;
int salary = 20000;
变量、常量
生活中有些值是固定不变,例如圆周率,性别,个人血型以及个人身份证号码;
但是还有些值是变化的,比如:年龄,价格,体重以及收入薪资;
不变的值,在C语言中用常量的概念来表示,变化的值C语言中用变量来表示。
定义变量的方法
int age = 150;
float weight = 45.5f;
char ch = 'w';
变量的分类
- 局部变量
- 全局变量
#include <stdio.h>
int global = 2022;//全局变量
int main()
{
int local = 2019;//局部变量
//下面定义的global会不会有问题?
int global = 2020;//局部变量
printf("global = %d\n", global);
return 0;
}
总结:
上面的局部变量global变量的定义其实没有什么问题的!
当局部变量和全局变量同名的时候,局部变量优先使用。(建议不要相同-容易误会,产生bug)
变量的使用
#include <stdio.h>
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
printf("输入两个操作数:>");
scanf("%d %d", &num1, &num2);
sum = num1 + num2;
printf("sum = %d\n", sum);
return 0;
}
C语言语法规定,变量要定义在代码块的最前面
这里在程序中出现了scanf语句以及printf语句,分别为输入、输出语句,那么具体内容为:
printf语句
输入输出函数(printf 和 scanf)是C语言中非常重要的两个函数,也是学习C语言必学的两个函数。在C语言程序中,几乎没有一个程序不需要这两个函数,尤其是输出函(printf)。输出函数的功能是将程序运行的结果输出到屏幕上,而输入函数的功能是通过键盘给程序中的变量赋值。可以说输入输出函数是用户和计算机交互的接口。其中 printf 的功能很强大,用法很灵活,比较难掌握;而 scanf 的用法相对比较固定,但也有很多需要注意的地方。
printf 函数的原型为:
# include <stdio.h>
int printf(const char *format, ...);
printf的格式一共有四种:
(1)printf("字符串\n");
# include <stdio.h>
int main(void)
{
printf("Hello World!\n"); // \n表示换行
return 0;
}
其中 \n
表示换行的意思。它是一个转义字符,前面在讲字符常量的时候见过。其中 n 是“new line”的缩写,即“新的一行”。
此外需要注意的是,printf 中的双引号和后面的分号必须是在英文输入法下。双引号内的字符串可以是英文,也可以是中文。
(2) printf("输出控制符",输出参数);
# include <stdio.h>
int main(void)
{
int i = 10;
printf("%d\n", i); /*%d是输出控制符,d 表示十进制,后面的 i 是输出参数*/
return 0;
}
这条printf语句的意思是将变量 i 以十进制输出
那么现在有一个问题:i 本身就是十进制,为什么还要将 i 以十进制输出呢?
因为程序中虽然写的是 i=10,但是在内存中并不是将 10 这个十进制数存放进去,而是将 10 的二进制代码存放进去了。计算机只能执行二进制 0、1 代码,而 0、1 代码本身并没有什么实际的含义,它可以表示任何类型的数据。所以输出的时候要强调是以哪种进制形式输出。所以就必须要有“输出控制符”,以告诉操作系统应该怎样解读二进制数据。
如果是 %x
就是以十六进制的形式输出,要是 %o
就是以八进制的形式输出。
(3) printf("输出控制符1 输出控制符2…", 输出参数1, 输出参数2, …);
# include <stdio.h>
int main(void)
{
int i = 10;
int j = 3;
printf("%d %d\n", i, j);
return 0;
}
//输出结果:10 3
输出控制符 1 对应的是输出参数 1,输出控制符 2 对应的是输出参数 2……编译、链接、执行
注意一下,为什么 10 和 3 之间有一个空格?因为上面 %d 和 %d之间有空格,printf 中双引号内除了输出控制符和转义字符 \n
外,所有其余的普通字符全部都原样输出。比如:
# include <stdio.h>
int main(void)
{
int i = 10;
int j = 3;
printf("i = %d, j = %d\n", i, j);
return 0;
}
//输出结果:i = 10, j = 3
编译、链接、执行后 i=
、,
、空格和 j=
全都原样输出了。此外需要注意的是:“输出控制符”和“输出参数”无论在“顺序上”还是在“个数上”一定要一一对应。
(4) printf("输出控制符 非输出控制符",输出参数);
到底什么是“输出控制符”,什么是“非输出控制符”?很简单,凡是以 %
开头的基本上都是输出控制符。
常用的输出控制符如下表所示:
控制符 | 说明 |
---|---|
%d | 按十进制整型数据的实际长度输出。 |
%ld | 输出长整型数据。 |
%md | m 为指定的输出字段的宽度。如果数据的位数小于 m,则左端补以空格,若大于 m,则按实际位数输出。 |
%u | 输出无符号整型(unsigned)。输出无符号整型时也可以用 %d,这时是将无符号转换成有符号数,然后输出。但编程的时候最好不要这么写,因为这样要进行一次转换,使 CPU 多做一次无用功。 |
%c | 用来输出一个字符。 |
%f | 用来输出实数,包括单精度和双精度,以小数形式输出。不指定字段宽度,由系统自动指定,整数部分全部输出,小数部分输出 6 位,超过 6 位的四舍五入。 |
%.mf | 输出实数时小数点后保留 m 位,注意 m 前面有个点。 |
%o | 以八进制整数形式输出,这个就用得很少了,了解一下就行了。 |
%s | 用来输出字符串。用 %s 输出字符串同前面直接输出字符串是一样的。但是此时要先定义字符数组或字符指针存储或指向字符串。 |
%x(或 %X 或 %#x 或 %#X) | 以十六进制形式输出整数,这个很重要。 |
%x、%X、%#x、%#X 的区别
一定要掌握 %x(或 %X 或 %#x 或 %#X),因为调试的时候经常要将内存中的二进制代码全部输出,然后用十六进制显示出来。下面写一个程序看看它们四个有什么区别:
# include <stdio.h>
int main(void)
{
int i = 47;
printf("%x\n", i);
printf("%X\n", i);
printf("%#x\n", i);
printf("%#X\n", i);
return 0;
}
/*输出结果:
2f
2F
0x2f
0X2F*/
从输出结果可以看出:如果是小写的 x
,输出的字母就是小写的;如果是大写的 X
,输出的字母就是大写的;如果加一个 #
,就以标准的十六进制形式输出。
最好是加一个 #
,否则如果输出的十六进制数正好没有字母的话会误认为是一个十进制数呢!总之,不加 #
容易造成误解。但是如果输出 0x2f 或 0x2F,那么人家一看就知道是十六进制。而且 %#x
和 %#X
中,笔者觉得大写的比较好,因为大写是绝对标准的十六进制写法。
如何输出%d、\和双引号
printf 中有输出控制符 %d
,转义字符前面有反斜杠 \
,还有双引号。那么大家有没有想过这样一个问题:怎样将这三个符号通过 printf 输出到屏幕上呢?
要输出 %d
只需在前面再加上一个 %
,要输出 \
只需在前面再加上一个 \
,要输出双引号也只需在前面加上一个 \
即可。程序如下:
# include <stdio.h>
int main(void)
{
printf("%%d\n");
printf("\\\n");
printf("\"\"\n");
return 0;
}
/*输出结果:
%d
\
""
*/
注意事项:
printf 是C语言中非常重要的一个函数。经过上面的学习我们发现,其实它并不难。只要多编程多练习,很快就能掌握。
知道为什么需要“输出控制符”。因为计算机中所有的数据都是二进制 0、1 代码,所以输出的时候要用“输出控制符”告诉计算机以什么形式将二进制数据显示出来。
输出控制符中,%d、%f、%s、%c 是最常用的,它们分别是输出整数、实数、字符串和字符的控制符。%.mf 虽然用得不多,但一定要重视。
最后 %x、%X、%#x、%#X 四种用法的区别只需要了解一下即可。
scanf语句
scanf的功能用一句话来概括就是“通过键盘给程序中的变量赋值”。该函数的原型为:
# include <stdio.h>
int scanf(const char *format, ...);
scanf("输入控制符", 输入参数);
功能:将从键盘输入的字符转化为“输入控制符”所规定格式的数据,然后存入以输入参数的值为地址的变量中。
下面为举得一个例子:
#include <stdio.h>
int main(void)
{
int i;
i = 10;
printf("i = %d\n", i);
return 0;
}
像这样写的,即直接给变量 i 赋一个值。但是这样写功能比较弱,因为这个值就变成一个“死值”了,它只能是 10,不可能是其他值,除非在程序中修改。很多时候我们希望这个值不是由程序员在程序中指定的,而是在程序运行的过程中由用户从键盘输入的。用户输入多少,变量i就是多少,这样程序的功能就更加灵活了。
那么利用scanf语句就可以实现在程序运行的过程中由用户键盘输入改变变量,具体如下:
# include <stdio.h>
int main(void)
{
int i;
scanf("%d", &i); //&i 表示变量 i 的地址,&是取地址符
printf("i = %d\n", i);
return 0;
}
“输入控制符”和“输出控制符”是一模一样的。比如一个整型数据,通过 printf 输出时用 %d
输出,通过 scanf 输入时同样是用 %d
。
要想将程序中的 scanf 行弄明白,首先要清楚的是:我们从键盘输入的全部都是字符。比如从键盘输入 123,它表示的并不是数字 123,而是字符 '1'、字符 '2' 和字符 '3'。这是为什么呢?
操作系统内核就是这样运作的。操作系统在接收键盘数据时都将它当成字符来接收的。这时就需要用“输入控制符”将它转化一下。%d
的含义就是要将从键盘输入的这些合法的字符转化成一个十进制数字。经过 %d 转化完之后,字符 123 就是数字 123 了。
第二个要弄清楚的是:&
是一个取地址运算符,&
后面加变量名表示“该变量的地址”,所以 &i
就表示变量 i 的地址。&i
又称为“取地址i”,就相当于将数据存入以变量 i 的地址为地址的变量中。
那么以变量 i 的地址为地址的变量是哪个变量呢?就是变量 i。所以程序中 scanf 的结果就把值 123 放到变量i中。
综上所述,scanf 语句的意思就是:从键盘上输入字符 123,然后 %d
将这三个字符转化成十进制数 123,最后通过“取地址 i”找到变量 i 的地址,再将数字 123 放到以变量 i 的地址为地址的变量中,即变量 i 中,所以最终的输出结果就是 i=123
。
注意,为什么不直接说“放到变量i中”?而是说“放到以变量 i 的地址为地址的变量中”?因为这么说虽然很绕口,但是能加强对 &i 的理解,这么说更能表达 &i 的本质和内涵。很多人在学习 scanf 的时候,经常将“变量 i”和“变量 i 的地址”混淆,从而思维开始混乱,等深刻了解 &i 的含义之后就可以不那么说了。
以上是 scanf 的最简单用法,也是最常用、最基本、最重要的用法。这样通过 scanf 就可以在程序运行的过程中由用户来指定变量 i 的值,这与在程序中赋值相比较功能更强大。
注意事项:
scanf 的使用看似细节繁杂,但使用起来非常简单。就目前而言,只要掌握以下几点:1.在 scanf 的“输入参数”中,变量前面的取地址符&
不要忘记。
2.“输出控制符”和“输出参数”无论在“顺序上”还是在“个数上”一定要一一对应。
3.“输入控制符”的类型和变量所定义的类型一定要一致。对于从键盘输入的数据的类型,数据是用户输入的,程序员是无法决定的,所以在写程序时要考虑容错处理,这个稍后再讲。
4.使用 scanf 之前养成习惯先用 printf 提示输入
变量的作用域和生命周期
作用域
作用域(scope)是程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的
而限定这个名字的可用性的代码范围就是这个名字的作用域。
- 局部变量的作用域是变量所在的局部范围。
- 全局变量的作用域是整个工程。
生命周期
变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段
- 局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。
- 全局变量的生命周期是:整个程序的生命周期。
常量
C语言中的常量和变量的定义的形式有所差异。
C语言中的常量分为以下以下几种:
- 字面常量
- const 修饰的常变量
- #define 定义的标识符常量
- 枚举常量
#include <stdio.h>
//举例
enum Sex
{
MALE,
FEMALE,
SECRET
};
//括号中的MALE,FEMALE,SECRET是枚举常量
int main()
{
//字面常量演示
3.14;//字面常量
1000;//字面常量
//const 修饰的常变量
const float pai = 3.14f; //这里的pai是const修饰的常变量
pai = 5.14;//是不能直接修改的!
//#define的标识符常量 演示
#define MAX 100
printf("max = %d\n", MAX);
//枚举常量演示
printf("%d\n", MALE);
printf("%d\n", FEMALE);
printf("%d\n", SECRET);
//注:枚举常量的默认是从0开始,依次向下递增1的
return 0;
}
注意事项:
上面例子上的pai 被称为const 修饰的常变量, const 修饰的常变量在C语言中只是在语法层面限制了
变量pai 不能直接被改变,但是pai 本质上还是一个变量的,所以叫常变量。
字符串+转义字符串+注释
字符串
"hello bit.\n"
这种由双引号(Double Quote)引起来的一串字符称为字符串字面值(String Literal),或者简称字符
串。
注意事项:
字符串的结束标志是一个\0 的转义字符。在计算字符串长度的时候\0 是结束标志,不算作字符串内容。
#include <stdio.h>
#include <string.h>
//下面代码,打印结果是什么?为什么?(突出'\0'的重要性)
//数据在计算机上存储的时候,存储的是2进制
//ASCII 码
int main()
{
char arr1[] = "bit";
char arr2[] = {'b', 'i', 't'};
char arr3[] = {'b', 'i', 't', '\0'};
printf("%s\n", arr1);
printf("%s\n", arr2);
printf("%s\n", arr3);
printf("%d\n",strlen(arr1));//strlen - string length -计算字符串长度
printf("%d\n",strlen(arr2));
printf("%d\n",strlen(arr3));
return 0;
}
/*输出结果:
bit
bit烫烫烫烫蘠it
bit
3
随机数
3
*/
转义字符
如果在屏幕上打印一个目录:c:\code\test.c
代码能这么写吗?
#include <stdio.h>
int main()
{
printf("c:\code\test.c\n");
return 0;
}
实际上的输出结果如下所示:
这里就需要看一下转义字符了,转义字符的表面意思就是转变意思,具体转义字符如下表所示:
转义字符 | 说明 |
---|---|
\? | 在书写连续多个问号时使用,防止他们被解析成三字母词 |
\' | 用于表示字符常量' |
\" | 用于表示一个字符串内部的双引号 |
\\ | 用于表示一个反斜杠,防止它被解释为一个转义序列符 |
\a | 警告字符,蜂鸣 |
\b | 退格符 |
\f | 进纸符 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ddd | ddd表示1~3个八进制的数字。 如: \130 X |
\xdd | dd表示2个十六进制数字。 如: \x30 0 |
#include <stdio.h>
#include <string.h>
int main()
{
printf("%d\n",strlen("c:\test\32\test.c"));
printf("%c\n",'\32')
//\32 -- 32是2个8进制数字
//32作为8进制代表的那个十进制数字,作为ASCII码值,对应的字符
//32 -- >10进制 26 ->作为ASCII码值代表的字符
return0;
}
/*输出结果:
13
->
*/
注释
1、代码中有不需要的代码可以直接删除,也可以注释掉
2、代码中有些代码比较难懂,可以加一下注释文字
具体样例如下:
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
/*C语言风格注释
int Sub(int x, int y)
{
return x-y;
}/
*/
int main()
{
//C++注释风格
//int a = 10;
//调用Add函数,完成加法
printf("%d\n", Add(1, 2));
return 0;
}
注释有两种风格:
- C语言风格的注释 /xxxxxx/
缺陷:不能嵌套注释 - C++风格的注释 //xxxxxxxx
可以注释一行也可以注释多行
- 感谢你赐予我前进的力量