嵌入式全栈开发学习笔记---C语言(数据类型/控制结构)
这篇博文的目的是复习C语言,以30多个编程题作为复习要点,这30多个编程题基本涵盖了C语言所有的内容了,只要你掌握了这30多个编程题,那么你的C语言基本就没什么问题了。注意:由于本专栏是嵌入式全栈开发专栏,为了我们能熟悉以后实际工作中的开发环境,我们写C语言全部在Linux中的vim编辑器中写,这么做事为了我们能够熟练掌握Linux系统的常用命令以及Linux上的vim编辑器的常用工作命令,以达到
目录
上一篇我们介绍了如何在Linux上编写和编译C程序,这一节我们正式复习介绍C语言的知识点
数据类型
我们做开发最重要的是记住:
每个数据类型所占的字节长度和每一种数据类型所能表示的数据范围;
内存是以字节为单位,一个字节由8位二进制数组成;
数据类型的长度
64位和32位的计算机的数据类型所占的字节长度有所差别。如果我们不知道或者忘记了某个数据类型在64位或者32位计算机上的数据类型长度,我们可以用sizeof来计算。
sizeof对应的占位符是“%lu”,其中u就是unsigned无符号,因为计算长度不可能是负数;
以下是在32位的操作系统上计算的结果:
如果是在64位的操作系统上,long应该是8个字节。
整数,在目前绝大多数机器上占4个字节。TC环境中是2个字节。
字符,一般是1个字节长,用来表示256个ASCII字符,或者0~255的整数。
以上基本的数据类型长度一定要牢记!
笔试题1
注意:很多人会误认为sizeof是一个函数,但其实它是一个关键字,用于计算数据类型的长度。
这一点大家一定要牢记,这里有一个笔试题能够证明sizeof是关键字而不是函数:
题目:
int a=1;
Printf(“%lu\n”,sizeof(a++));
Printf(“a=%d\n”,a);
提问:输出的a等于多少?
答案是a=1;
因为sizeof是关键字,而不是函数,它只用来计算a的数据类型长度。a++不是参数,它不会像函数的参数一样传过去某个函数的定义内执行完后返回,所以a还是1。
这个题目也请大家牢记,有可能将来在某份笔试题中出现!
数据类型的范围
有符号的数据类型范围
最高位表示符号位,0是正数,1是负数
无符号的数据类型范围
浮点型数据类型范围
浮点型数据类型所能表达的数据范围一般比整型的数据类型大,因为浮点型和整形数据类型在内存中存储的方式不一样。
浮点数表示法将一个数分为小数部分和指数部分并分别存储。
因此尽管7.00和7有相同的值,但它们的存储方式不同。下图只是示意图,计算机的内部存储使用二进制数字,它使用2的幂而非10的幂
笔试题2
这里也有一个笔试题:
问:-128-1=?
答案是127
因为char类型的数据范围是-128~+127,如果等于-129的话明显溢出了。我们要记住负数在内存中的储存方式:负数在内存中以补码形式存在。
那么我们来分析一下这道题,
首先-128的源码是1000 0000,
反码是(符号位不动,1变成0,0变成1)1111 1111,
补码(反码+1,符号位不动)原本应该是11000 0000但是最左边的1溢出了,所以最终是1000 0000,
因此负数的源码和补码是一样的。
而-1的源码是1000 0001,
反码1111 1110
补码1111 1111
最终-128-1那就是-128的补码1000 0000加上-1的补码1111 1111,结果就是127
这道题也希望大家能够牢记,因为这是比较经典的题目,如果你能掌握的话,那今后其他笔试题也能举一反三。
笔试题3
问:
int i = -20;
unsigned int j = 10;
i+j =?
答案:4294967286
为什么呢?
这是一道混合运算,如果你直接填-10,那就是0分不得了。
i是int类型,j是unsigned int类型,在C语言中,不同的数据类型是不能运算的,但是编译的时候没有报错,那是因为当它检测到运算符号两边的数据类型不一样的时候,它会帮我们统一两边的数据类型,即帮我们转换数据类型。
比如,char ch和int num两个变量相加ch+int,我们知道char占1个字节,int占4个字节,如果运算的时候,int转换成char就要丢弃3个字节,那数据就失真了,已经不是原来的数值了,所以int不能转换成char。
这种情况下,char就能够转换成int类型,char补上三个字节
我们得出一个结论:混合运算,把表示范围小的转换成表示范围大的。
那如果说是char和unsigend char,哪一个表示的范围更大呢?
明显是unsigned char的范围更大,因为unsigend char的范围是0~255,而char表示的范围是-128~127。
所以我们再得出一个结论:混合运算,把有符号的转换成无符号的。
因此我们这道笔试题应该这样解:
首先,要知道负数在内存中是以补码的形式出现的。
-20的原码是1000 0000 0000 0000 0000 0000 0001 0100
反码:1111 1111 1111 1111 1111 1111 1110 1011
补码:1111 1111 1111 1111 1111 1111 1110 1100
现在我们要将-20的补码从int型转换成unsigned char型:
补码:1111 1111 1111 1111 1111 1111 1110 1100原来的最高位1是表示符号位,现在既然要转换成无符号的int,那这个最高位的1就不再表示符号了,而是直接带入计算就行了。
我们用计算器算一下这个数多大:
这个数转换成十进制是:4294967276
加上unsigned int 10就是4294967286
那最终这道题的答案就是:4294967286
我们再看一道微软公司的笔试题:
笔试题4
问:
unsigned int a =3;
-1*a=?
答案是:4294967293
-1明显是由符号的数字,a是无符号的数字,所以我们一定是要把-1转换成无符号的数字,然后再进行计算。但是这道题有点刁钻,因为结果已经超出了unsigned int的最大值,最终超出的部分数值要丢弃。
解:
-1的原码是1000 0000 0000 0000 0000 0000 0000 0001
反码:1111 1111 1111 1111 1111 1111 1111 1110
补码:1111 1111 1111 1111 1111 1111 1111 1111
补码转换成无符号的数据,高位的1不再表示符号位,直接计算
12,884,901,885明显超出了unsigned int的最大范围4294967295
所以超出的部分数值要丢弃!
无符号数的原码和补码相等,都是0000 0000 0000 0000 0000 0000 0000 0011,不需要转换!
丢弃了高位的两位10后,得出的结果是4294967293
笔试题5
问:
小数值1.5625的二进制表示是_____(阿里巴巴2015笔试题)
答案是:1.1001
小数部分我们要用到乘2取整法
这些题也希望大家能够牢记,因为这是比较经典的题目,如果你能掌握的话,那今后其他笔试题也能举一反三。
常量
数据的表现形式有两种:常量 变量
常量是一种在程序中保持固定类型和固定值的数据。编译系统从数据形式上可以区分(除枚举类型外)
比如宏常量:
常量的种类
常量分为:
a、整型常量
b、实型常量
c、字符常量
d、字符串常量
e、枚举常量(后面再讲)
有时候我们看代码我们会看到这样的一些标志:
常量标志
1、整型常量
十进制常量:18、-31,
long int型常量:123l、123L、123456l、123456L;(这个l或者L只是说明这个数是long类型的常量,写不写都没关系)
unsigned int型常量:123u、 123U(这个u或者U只是说明这个数是unsisgned类型的常量,写不写都没关系);
根据实际数据大小确定int型还是long型。
2、以数字“0”开始的整型常量是八进制数
比如022、-037。注意010(八进制)和10(十进制)大小不一样。因为八进制并不常用,所以此种表示法比较少见。
3、以“0x”或者“0X”开始的整型常量是十六进制
A~F和a~f用来表示十进制的10~15,十六进制的形式比较常用:0x12、-0x1F, -0x1f...
4、实型常量
十进制小数形式:123.45、456.78;
指数形式:1e-2、4.5e3(4.5乘以10的3次方);
float型常量 123.45f、 456.78F、1e-2f、4.5e3F;
long double型常量 123.45l、 456.78L;缺省为double
5、字符常量的表示方法
比如:'a'、'A'、'5'、'%'、'$';单引号内只能有一个字符,除非用“\”开头
注意:'5'和5的区别
字符在内存中是以ASCII码存储的,比如’a’它其实也是以数字的形式在内存中存储的,我们想要查它对应的这个数值,我们就可以去查找ASCII码表,可以看到’a’对应的数值是97,那它在内存中是以97的二进制数值存储的。
6、用'\'开头的字符为转义字符
例如,'\n',代表1个字符
7、字符串常量
字符串常量是一对双引号括起来的字符序列;
合法的字符串常量:“How do you do.”、”CHINA”、”a”、”$123.45”;
可以用printf直接输出一个字符串,如printf(“How do you do.”);
C规定:在每一个字符串常量的结尾加一个“字符串结束标志”,以便系统据此判断字符串是否结束。C规定以字符'\0'作为字符串结束标志,用sizeof求字符串长度的时候不计算“\0”,但是字符串数组大小要计算“\0”。
如:如果有一个字符串常量”CHINA” ,实际上在内存中是:
C |
H |
I |
N |
A |
\0 |
它占内存单元不是5个字符,而是6个字符,最后一个字符为'\0'。但在输出时不输出'\0'。
变量
普通变量
变量是在程序执行过程中可以改变。
只读变量const关键字(笔试重点)
上一次我们复习了一个非常重要的关键字sizeof,这次我们再复习一个关键字const。
const这个关键字是介于常量和变量之间的,很多人把它理解为是将变量定义为常量,但其实它是用来把变量定义为只读变量。
将变量num定义成了只读变量,那就不能再直接通过num这个变量名来修改对应内存的值,如果硬改就会报错
硬方法不行,那就来软办法:
我们可以通过地址(指针)来间接修改num对应内存的值。
我们先取出num的地址
然后修改并打印出来看看
这样我们就把它修改成1000了
后面我们复习指针的时候还会继续剖析这个const
输入和输出
1、格式输入函数
scanf(格式控制字符串, 地址表列);
scanf("%d %f”, &a,&b);
注:&取a第一个字节的地址;%表示获取的意思
2、格式输出函数
printf(格式控制字符串, 输出项表列);
函数原型:int printf(const char *format, ...);
注:“...”表示可变参数。
注意:scanf不能加“\n”!输出printf可以加“\0”
格式输出的占位符
%d或%i按十进制有符号整数输出,正数的符号省略
%u按十进制无符号整数输出
%o按八进制无符号整数输出(不输出前导0)
%x或X按十六进制无符号整数输出(不输出前导符0x)
%c按字符型数据输出
%s按字符串数据输出
%f按小数形式输出(6位小数)
%e或E按指数形式输出实数
%%输出%本身
%g或G选用%f或%e格式中输出宽度较短的一种格式,不输出无意义的0
%p以十六进制输出(输出带有0x)
以上这些常用的占位符要记忆!
C语言运算符
1、算术运算符 (+ - * /(取整) %(取余))
2、关系运算符 (> < == >= <= !=(不等于))
3、逻辑运算符 (!(非,用于逻辑取反) &&(短路与) ||(短路或))
短路原则:比如“if(表达式1 ||表达式2)”:如果表达式1已经成立,则不会执行表达式2。这个到我们学C++的时候会有个很重要的结论,先记住它。
4、位运算符 (<< >> ~(非,按位取反) |(逻辑或,按位加,通常用于位置1) ∧(异或,同0,异1) &(逻辑与,按位乘,通常用于位清零))
!和 ~ 取反的区别在于一个逻辑取反,一个是按位取反,比如!5就是非0变成0,~5就是00000101变成1111 1010,结果就是!5=0,~5=250。
5、赋值运算符 (=及其扩展赋值运算符)
6、条件运算符 (?:)
通常用于比较,也叫三目运算符,比如int max=(a>b)?a:b;,a和b先比较,如果条件成立,则把a赋值给max,如果不成立,则把b赋值给max。注意:int max=(a>b)?a:b=3;这句代码是错误的,记住,在C语言中,条件运算符不能作为左值使用。(在C++中可以!)
7、逗号运算符 (,)结合for循环来讲
8、指针运算符 (*和&)
9、求字节数运算符(sizeof)
10、强制类型转换运算符( (类型) )
11、分量运算符(. ->)
12、下标运算符([])
13、其他 (如函数调用运算符())
注:7~13后面再详细复习
运算符优先级
优先级从高到低排列如下:
函数符号() 和数组下标[] (优先级最高)
单目运算符
算数运算符
移位运算符
关系运算符
逻辑运算符
三目运算符 (优先级最低)
注意:单目运算符、双目运算符,三目运算符怎么区分?
根据参与运算的参数判断,比如说a++,这个++就是单目运算符,因为它只需要一个参数a;而a+b的+属于双目运算符,因为它需要两个参数;int max=(a>b)?a:b;的?:就属于三目运算符,因为它需要三个参数。
能背下优先级表的人凤毛麟角
用括号来控制运算顺序更直观、方便,并减少出错的概率,但是笔试的时候会考!所以建议背下来。
自增自减运算符(笔试重点)
1、i++/i--
等价于i = i + 1 或者 i = i - 1
执行i所在语句之后,对i的值加一/减一
2、++i/--i
等价于i = i + 1 或者 i = i - 1
执行i所在语句之前,对i的值加一/减一
注意:C语言前置++不能做左值,但是在C++里面是可以的。
所以我们写个笔试题,并用g++编译看看结果(g++是C++编译工具,gcc是C语言的编译工具)
笔试题6
问:
(1)
a=1;
a += a++;
a=?
(2)
a=1;
a += ++a;
a=?
(3)
a=1;
++a += a;
a=?
(4)
a=1;
++a += a++;
a=?
(5)
a=1;
++a += ++a;
a=?
答案是:
(1)a=3
(2)a=4
(3)a=4
(4)a=5
(5)a=6
解:
(1)a=a+a=1+1=2, a+1=2+1=3;
(2)单目运算符的优先级高,先运行前置++,a+1=1+1=2,a=a+a=2+2=4;
(3)单目运算符的优先级高,先运行前置++,a+1=1+1=2,a=a+a=2+2=4;
(4)单目运算符的优先级高,先运行前置++,a+1=1+1=2,a=a+a=2+2=4,a+1=4+1=5,这条代码在不同编译器编译出来的结果可能不相同,有的编译器编译出来的结果是4,所以大家自己写代码的时候不要写这种有歧义的代码,这里只是给大家演示一下。
(5)单目运算符的优先级高,先运行前置++,a+1=1+1=2,a+1=2+1=3,a=a+a=3+3=6,这条代码在不同编译器编译出来的结果可能不相同,有的编译器编译出来的结果是5。
g++编译结果为
如果用gcc编译的话它会报错,左值无效,
所以,C语言前置++不能做左值,但是在C++里面是可以的。
笔试题7
问:以下两种情况输出的i和k分别是多少?
(1)
int i = 3, k;
k = (++i) + (++i) + (i++);
printf("i = %d, k = %d\n", i, k);
(2)
i = 3;
k = (++i) + (++i) + (++i);
printf("i = %d, k = %d\n", i, k);
以上代码在windows和Linux系统中输出的结果可能不同,
第一种编译方式是将三个参数当成一个整体表达式,编译结果是:
(1)单目运算符的优先级高,先运行完前置++再进行双目运算符的加法运算,i初始值为3,i+1=4, i+1=5,输出k=5+5+5=15,i+1=6,最终i=6
(2)i初始值为3,i+1=4, i+1=5,i+1=6,输出k=6+6+6=18,i=6
第二种编译方式是将前两个参数当成一个整体表达式,然后将其结果与第三个参数进行运算,编译结果是:
(1)i+1=4, i+1=5, 5+5=10, 输出k= 10+5=15 ,i+1=6
(2)i+1=4, i+1=5,5+5=10, i+1=6,输出k=10+6=16, i=6
这种有歧义的代码不需要纠结它的编译方式到底是哪一个,笔试的时候遇到,就只能看我们跟面试官的缘分了。
以上就是这篇内容,如想了解更多,欢迎订阅本专栏!
如有问题可评论区或者私信留言,如果想要进交流群请私信!
上一篇C语言的运算符及其优先级,这一篇开始复习C语言的控制结构
说明:我们学过单片机的一般都是有C语言基础的了,网上关于C语言的资料有很多,大家如果对C语言不熟悉的话可以先去详细学一下,再以这篇博文作为复习资料学习。
这篇博文的目的是复习C语言,我们会陆续以30多个编程题作为复习要点,这30多个编程题基本涵盖了C语言所有的内容了,只要你掌握了这30多个编程题,那么你的C语言基本就没什么问题了。
注意:由于本专栏是嵌入式全栈开发专栏,为了我们能熟悉以后实际工作中的开发环境,我们写C语言全部在Linux中的vim编辑器中写,这么做事为了我们能够熟练掌握Linux系统的常用命令以及Linux上的vim编辑器的常用工作命令,以达到对口训练的目的!
vim编辑器的一些工作命令在上一篇博文中已经详细介绍过了,如果不了解可以先去看看。
我们正式开始:
程序的三种控制结构
顺序结构
-
表达式语句
-
空语句
-
函数调用语句
-
复合语句
选择结构
(1)if语句
基本语法
if(表达式1)
{
语句1
}
else if(表达式2)
{
语句2
}
……
else if(表达式m)
{
语句m
}
else
语句n
匹配规则:
else总是与它上面的,最近的,统一复合语句中的,未配对的if语句配对;
当if和else数目不同时,可以加花括号来确定配对关系
思考:
数值如何跟零进行比较?
if (a == 0){}(这种写法是对的,也是我们通常的写法,但是有一个缺陷,就是写代码的时候容易忘记其中一个等号,编译器不会报错,很难发现)
if (0 == a){}(推荐这种写法,忘记敲一个等号时编译器会给我们报错)
if (a = 0){}(错)
if (0 = a){}(错)
if语句有时候可以用switch语句替换。
(2)多分支语句switch
switch语句的格式:
switch (表达式)
{
case 常量表达式1:语句1; break;
case 常量表达式2:语句2; break;
…
case 常量表达式n:语句n; break;
default:语句n+1; break;
}
注意:default千万不要拼写错误,因为即使你写错了编译器也不会报错,可能会对整个程序造成影响。
思考:
exit(1)和break有什么区别?
exit(1)整个程序结束, break只结束循环或者switch语句的case;
return 0和return -1、return -1有什么区别?
这三个都是带有return,都是代表结束整个程序,
但是return 0是正常终止程序,
return -1和return 1是一样的,都是返回一个非零数值,表示非正常终止程序,这个数值我们可以写-128~127。
exit(1)和return -1或者return 1有什么区别?
它们都是表示非正常终止程序,只不过exit(1)要加头文件stdlib.h
循环结构
(1)while语句
while (表达式) 语句
当表达式为非0值时,执行while语句中的内嵌语句。其特点是:先判断表达式,后执行语句。
笔试题8
问:房价200万,每年涨10%;程序员工资年薪40万,不吃不喝存起来,问几年能买房?
解:
编译一下:
程序进入了死循环,为什么进入了死循环,哪里错了?
因为到死都买不起房啊!
其实也不用破防,因为现实中我们可以贷款或者啃老买房啊!
强调:Ctrl+C可以停止当前进程
程序死循环的时候,按Ctrl+C可以停止并退出编译
我们把每年的房价和积蓄慢慢打印出来看看:
结果请看下方视频:
while语句的循环结果
(3)do while语句
(用的比较少,这里复习就不详细讲了)
while语句有时候可以用for语句替换。当我们知道要循环的次数时,可以写成for循环。
(4)for语句
for循环基本语法
for(表达式1;表达式2;表达式3)
{
语句
}
笔试题9
问:以下这段代码的运行结果
for (i = 0, printf("1\n"); i < 5, printf("2\n"); i++,printf("3\n"))
{
printf(“helloworld\n”);
}
答案是它不停的打印:
为什么呢?
前一篇中我们提到逗号表达式,现在就来详细讲一下:
逗号表达式就是将两个表达式用逗号隔开,但是逗号两边的表达式算是一整个逗号表达式,并且只以表达式2的结果作为整个逗号表达式的最终的结果,比如int result=(1+1,2+2); 最后result=4。
所以上面这道笔试题就是:
判断部分i < 5, printf("2\n")的结果就是取了printf("2\n")的结果,因为'2'和'\n'是两个字符,所以printf函数的返回值是2,也就是说判断部分的结果永远是非零,只要是非零就是真,那for循环就是一直循环下去,就一直打印“helloworld”
它一进去for循环就先打印一个1,之后就是打印2,然后打印helloworld,再打印3,然后就是一直是2 helloworld 3 2 helloworld 3......
整个程序1只打印了一次,但是程序执行的太快刷屏了,没看到那个1。
两个关键字
(1)continue
continue结束本次循环,开始下一次循环。
比如下面这段代码如果没有continue的话原本是打印15次helloworld,但是加了continue之后,当j等于1也就是第二次循环的时候,没有打印出helloworld,直接跳到j++,打印第三次循环的helloworld,那么整个程序就只打印了10次helloworld。
(2)break
break结束这一层循环,如果循环有嵌套,不影响其它层的循环。
注意:break语句不能用于循环语句和switch语句之外的任何其他语句中。
我们把上一段代码中的continue换成break又会怎样呢?
当j等于1也就是第二次循环的时候,就直接退出j这层循环,开始i=1即i层循环的第二次循环。那么这段代码就是每层i循环中只执行一次j循环,那么最终只打印出5个helloworld。
循环效率
第一种(效率低,不推荐)
for (i = 0; i < 1000; i++)
{
for (j = 0; j < 2; j++)
{
........
}
}
第二种( 效率高,推荐)
for (i =0; i < 2; i++)
{
for (j = 0; j < 1000; j++)
{
........
}
}
结论:注意减少跨切循环层的次数,也就是说建议里层的循环次数比外层的循环次数多。
下节开始第一次编程题训练!
如有问题可评论区或者私信留言,如果想要进交流群请私信!
更多推荐
所有评论(0)