Linux C编程一站式学习笔记2
Posted 临风而眠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux C编程一站式学习笔记2相关的知识,希望对你有一定的参考价值。
Linux C编程一站式学习笔记 chap2 常量、变量和表达式
本书以C99为标准
一.继续hello world
- 加入更多注释的hello world
可以用
ctrl+(shift)+v
复制到vim里面
#include <stdio.h>
/*
* comment1
* main: generate some simple output
*/
int main(void)
printf(/* comment2 */"Hello, world.\\n"); /* comment3 */
return 0;
第一个注释的头尾两行是注释的界定符(Delimiter)/和/,中间两行开头的*号(Asterisk)并没有特殊含义,只是为了看起来整齐
把注释写到printf里面我第一次见哈哈哈
下面这样一样不会报错
-
使用注释的注意点:
- 注释不能嵌套(Nest)使用,就是说一个注释的文字中不能再出现
/*
和*/
了- 例如
/* text1 /* text2 */ text3 */
是错误的- 编译器只把
/* text1 /* text2 */
看成注释 - 后面的
text3 */
无法解析,因而会报错。
- 编译器只把
- 例如
- 有的C代码中有类似
// comment
的注释,两个/
斜线(Slash)表示从这里直到该行末尾的所有字符都属于注释,这种注释不能跨行,也不能穿插在一行代码中间。这是从C++借鉴的语法,在C99中被标准化
- 注释不能嵌套(Nest)使用,就是说一个注释的文字中不能再出现
-
上面那段程序中的
\\n
是转义字符C标准规定的转义字符
符合 解释 \\'
单引号’(Single Quote或Apostrophe) \\"
双引号" \\?
问号?(Question Mark) \\\\
反斜线\\(Backslash) \\a
响铃(Alert或Bell) \\b
退格(Backspace) \\f
分页符(Form Feed) \\n
换行(Line Feed) \\r
回车(Carriage Return) \\t
水平制表符(Horizontal Tab) \\v
垂直制表符(Vertical Tab) 如果在字符串字面值中要表示单引号和问号,既可以使用转义序列
\\'
和\\?
,也可以直接用字符’和?,而要表示\\或"则必须使用转义序列(Escape Sequence),因为\\字符表示转义而不表示它的字面含义,"表示字符串的界定符而不表示它的字面含义。-
可见转义序列有两个作用
-
把普通字符转义成特殊字符,例如把字母n转义成换行符
-
把特殊字符转义成普通字符,例如\\和"是特殊字符,转义后取它的字面值。
像
"Hello, world.\\n"
这种由双引号(Double Quote)引起来的一串字符称为字符串字面值(String Literal),或者简称字符串
-
-
-
C语言规定了几个控制字符,不能用键盘直接输入,因此采用\\加字母的转义序列表示。
\\a
是响铃字符- 在字符终端下显示这个字符的效果是PC喇叭发出嘀的一声(在Linux终端里试了试真的有!)
- 在图形界面终端下的效果取决于终端的实现。
- 在终端下显示
\\b
和按下退格键的效果相同。 \\f
是分页符,主要用于控制打印机在打印源代码时提前分页,这样可以避免一个函数跨两页打印。\\n
和\\r
分别表示Line Feed和Carriage Return,这两个词来自老式的英文打字机,Line Feed是跳到下一行(进纸,喂纸,有个喂的动作所以是feed),Carriage Return是回到本行开头(Carriage是卷着纸的轴,随着打字慢慢左移,打完一行就一下子移回最右边),现在Windows上的文本文件用\\r\\n
做行分隔符,许多应用层网络协议(如HTTP)也用\\r\\n
做行分隔符,而Linux和各种UNIX上的文本文件只用\\n
做行分隔符。- 在终端下显示
\\t
和按下Tab键的效果相同,用于在终端下定位表格的下一列, \\v
用于在终端下定位表格的下一行。\\v
比较少用,\\t
比较常用,以后将“水平制表符”简称为“制表符”或Tab。
-
程序中很多地方的空格、Tab还有换行多一个少一个往往是无关紧要的,例如不缩进不会影响程序的结果,
main
后面多几个空格没影响,但要注意以下几点-
int
和main
之间至少要有一个空格分隔开 -
include
必须单独占一行 -
字符串字面值中的空格也算一个字符,也会出现在输出结果中,而,不会对编译的结果产生任何影响,
-
好的代码风格要求缩进整齐,每个语句一行,适当留空行
-
二.常量
-
常量(Constant)是程序中最基本的元素
- 字符(Character)常量
- 整数(Integer)常量
- 浮点数(Floating Point)常量
- 枚举常量
-
和上一节一起学一学
#include <stdio.h> /* * comment1 * main: generate some simple output */ int main(void) printf("123\\r\\r\\r\\f"); printf(/* comment2 */"Hello, world.\\t Hello t \\a\\n"); /* comment3 */ printf("s\\v①\\v②\\v③"); printf("character: %c\\n integer: %d\\n floating point: %f\\n", '', 34,3.14); return 0;
输出结果
123 Hello, world. Hello t s ① ② ③character: integer: 34 floating point: 3.140000
\\v
暂时没搞明白 -
字符常量要用单引号括起来,例如上面的
''
,-
注意单引号只能括一个字符而不能像双引号那样括一串字符
-
字符常量也可以是一个转义序列,例如
'\\n'
,这时虽然单引号括了两个字符,但实际上只表示一个字符 -
比如把上面中的字符串常量改为
\\n
#include <stdio.h> /* * comment1 * main: generate some simple output */ int main(void) printf("123\\r\\r\\r\\f"); printf(/* comment2 */"Hello, world.\\t Hello t \\a\\n"); /* comment3 */ printf("s\\v①\\v②\\v③"); printf("character: %c\\n integer: %d\\n floating point: %f\\n", '\\n', 34,3.14); return 0;
输出结果会多换一行
123 Hello, world. Hello t s ① ② ③character: integer: 34 floating point: 3.140000
-
-
字符串字面值中使用转义序列有一点区别
-
如果在字符常量中要表示双引号"和问号?,既可以使用转义序列
\\"
和\\?
,也可以直接用字符"和?可以看看这个:关于C语言中的’?'和%%,挺有意思的
-
而要表示’和\\则必须使用转义序列
-
-
来看
printf("character: %c\\ninteger: %d\\nfloating point: %f\\n", '', 34, 3.14);
-
printf
中的第一个字符串称为格式化字符串(Format String),它规定了后面几个常量以何种格式插入到这个字符串中 -
在格式化字符串中%号(Percent Sign)后面加上字母c、d、f分别表示字符型、整型和浮点型的转换说明(Conversion Specification),转换说明只在格式化字符串中占个位置,并不出现在最终的打印结果中,这种用法通常叫做占位符(Placeholder)。
-
这也是一种字面意思与真实意思不同的情况,但是转换说明和转义序列又有区别:
-
转义序列是编译时处理的
-
而转换说明是在运行时调用
printf
函数处理的。 -
源文件中的字符串字面值是
"character: %c\\ninteger: %d\\nfloating point: %f\\n"
,\\n
占两个字符,而编译之后保存在可执行文件中的字符串是character: %c换行integer: %d换行floating point: %f换行
,\\n
已经被替换成一个换行符,而%c
不变,然后在运行时这个字符串被传给printf
,printf
再把其中的%c
、%d
、%f
解释成转换说明。 -
关于printf中要用%%来输出一个%,而不是%
%%与转义字符无关,它与printf如何处理格式说明符有关,转义字符对所有字符串有效,并在编译时完成,格式说明符(%)仅在一些函数使用,并在运行时使用。
在字符串或单个字符中,%都能代表它本身,不需要也不能用%%来表示一个%,只在像printf这样使用%作为格式说明符的函数中规定使用%%来明确表示一个%字符。
呃 ,这样写代码好像不大好
改了改也不大行
-
-
-
注意区分:
"5"
、'5'
、5
,第一个是字符串字面值,第二个是字符,第三个是整数
三.变量
-
变量(Variable)是计算机存储器中的一块命名的空间,可以在里面存储一个值(Value),存储的值是可以随时变的。
-
变量也有不同的类型,其类型决定了它所占的存储空间的大小。
-
以下四个语句定义了四个变量
fred
、bob
、jimmy
和tom
,它们的类型分别是字符型、整型、浮点型:char fred; int bob; float jimmy; double tom;
-
给变量起名最好见名知意
char firstletter; char lastletter; int hour, minute;
我们可以猜得到这些变量是用来存什么的,前两个变量的取值范围应该是
'A'~'Z'
或'a'~'z'
,变量hour
的取值范围应该是0~23
,变量minute
的取值范围应该是0~59
此例中我们也看到两个相同类型的变量(
hour
和minute
)可以一起声明 -
在C语言中有些有特殊意义的称为关键字(Keyword)或保留字(Reserved Word)的单词。C99规定的关键字有:
auto break case char const continue default do double else enum extern float for goto if inline int long register restrict return short signed sizeof static struct switch typedef union unsigned void volatile while _Bool _Complex _Imaginary
-
变量起名规则
- 关键字不能作为变量名
- C语言规定必须以字母或下划线_(Underscore)开头,后面可以跟若干个字母、数字、下划线,但不能有其它字符。
- 一般来说应避免使用以下划线开头的标识符,以下划线开头的标识符只要不和C语言关键字冲突的都是合法的,但是往往被编译器用作一些功能扩展,C标准库也定义了很多以下划线开头的标识符,除非对编译器和C标准库特别清楚,一般应避免使用这种标识符,以免造成命名冲突。
- 例如这些是合法的变量名:
Abc
、__abc__
、_123
。 - 但这些是不合法的变量名:
3abc
、ab$
。 - 其实这个规则不仅适用于变量名,也适用于所有可以由程序员起名的语法元素,例如以后要讲的函数名、宏定义、结构体成员名等,在C语言中这些统称为标识符(Identifier)。
四.赋值
-
定义了变量之后,我们要把值存到它们所表示的存储空间里,可以用赋值(Assignment)语句实现:
char firstletter; int hour, minute; firstletter = 'a'; /* give firstletter the value 'a' */ hour = 11; /* assign the value 11 to hour */ minute = 59; /* set minute to 59 */
- 注意变量一定要先声明后使用,编译器必须先看到变量声明,才知道
firstletter
、hour
和minute
是变量名,各自代表一块存储空间。 - 另外,变量声明中的类型表明这个变量代表多大的一块存储空间,这样编译器才知道如何读写这块存储空间。
- 还要注意,这里的等号不表示数学里的相等关系,和1+1=2的等号是不同的,这里的等号表示赋值。在数学上不会有
i=i+1
这种等式成立,而在C语言中表示把变量i
的存储空间中的值取出来,再加上1,得到的结果再存回i
的存储空间中。再比如,在数学上a=7
和7=a
是一样的,而在C语言中后者是不合法的。
- 注意变量一定要先声明后使用,编译器必须先看到变量声明,才知道
-
定义一个变量,就是分配一块存储空间并给它命名;
- 变量的最小存储单位是字节(Byte),不同类型的变量所占的存储空间大小是不同的,数据表示方式也不同
- 在C语言中
char
型变量占一个字节,其它类型的变量占多少字节在不同平台上有不同的规定
-
给一个变量赋值,就是把一个值保存到这块存储空间中。
-
变量的定义和赋值也可以一步完成,这称为变量的初始化(Initialization),例如要达到上面代码的效果也可以这样写:
char firstletter = 'a'; int hour = 11, minute = 59;
在初始化语句中,等号右边的值叫做Initializer,例如上面的
'a'
、11和59。注意,初始化是一种特殊的声明,而不是一种赋值语句。
就目前来看,先定义一个变量再给它赋值和定义这个变量的同时给它初始化所达到的效果是一样的,C语言的很多语法规则既适用于赋值也适用于初始化,(但后续学习会了解到它们之间的不同)
-
既然可以为变量的存储空间赋值,就应该可以把值取出来用,现在我们取出这些变量的值用
printf
打印:printf("Current time is %d:%d", hour, minute);
变量名用在等号左边表示赋值,而用在
printf
中表示把它的存储空间中的值取出来替换在那里。
五.表达式
-
常量和变量都可以参与加减乘除运算,例如
1+1
、hour-1
、hour * 60 + minute
、minute/60
等。- 这里的+ - * /称为运算符(Operator)
- 而参与运算的常量和变量称为操作数(Operand)
- 上面四个由运算符和操作数所组成的算式称为表达式(Expression)。
- 运算的优先级(Precedence)
- 和数学上规定的一样,
hour * 60 + minute
这个表达式应该先算乘再算加,也就是说运算符是有优先级的 *
和/
是同一优先级,+和-是同一优先级,*
和/
的优先级高于+和-。- 对于同一优先级的运算从左到右计算,如果不希望按默认的优先级计算则要加()括号(Parenthesis)。例如
(3+4)*5/6
应先算3+4,再算*5,再算/6。
- 和数学上规定的一样,
-
表达式语句
-
在任意表达式后面加个;号也是一种语句,称为表达式语句。例如
hour * 60 + minute;
这是个合法的语句,但这个语句在程序中起不到任何作用,把hour的值和minute的值取出来加乘,得到的计算结果却没有保存
再比如:
int total_minute; total_minute = hour * 60 + minute;
这个语句就很有意义,把计算结果保存在另一个变量
total_minute
里。事实上等号也是一种运算符,称为赋值运算符,赋值语句就是一种表达式语句,等号的优先级比+和*都低,所以先算出等号右边的结果然后才做赋值操作,整个表达式total_minute = hour * 60 + minute
加个;号构成一个语句
-
-
任何表达式都有值和类型两个基本属性。
-
hour * 60 + minute
的值是由三个int
型的操作数计算出来的,所以这个表达式的类型也是int
型。同理,表达式total_minute = hour * 60 + minute
的类型也是int
,它的值是多少呢?C语言规定等号运算符的计算结果就是等号左边被赋予的那个值,所以这个表达式的值和hour * 60 + minute
的值相同,也和total_minute
的值相同。 -
等号运算符还有一个和+ - * /不同的特性,如果一个表达式中出现多个等号,不是从左到右计算而是从右到左计算,例如:
int total_minute, total; total = total_minute = hour * 60 + minute;
计算顺序是先算
hour * 60 + minute
得到一个结果,然后算右边的等号,就是把hour * 60 + minute
的结果赋给变量total_minute
,这个结果同时也是整个表达式total_minute = hour * 60 + minute
的值,再算左边的等号,即把这个值再赋给变量total
。同样优先级的运算符是从左到右计算还是从右到左计算称为运算符的结合性(Associativity)。
+ - * /
是左结合(从左到右)的,等号是右结合(从右到左)的
-
-
现在我们总结一下到目前为止学过的语法规则:
表达式 → 标识符 表达式 → 常量 表达式 → 字符串字面值 表达式 → (表达式) 表达式 → 表达式 + 表达式 表达式 → 表达式 - 表达式 表达式 → 表达式 * 表达式 表达式 → 表达式 / 表达式 表达式 → 表达式 = 表达式 语句 → 表达式; 语句 → printf(表达式, 表达式, 表达式, ...); 变量声明 → 类型 标识符 = Initializer, 标识符 = Initializer, ...; (= Initializer的部分可以不写)
表达式可以是单个的常量或变量,也可以是根据以上规则组合而成的更复杂的表达式。以前我们用
printf
打印常量或变量的值,现在可以用printf
打印更复杂的表达式的值,例如:printf("%d:%d is %d minutes after 00:00\\n", hour, minute, hour * 60 + minute);
编译器在翻译这条语句时,首先根据上述语法规则把这个语句解析成下图所示的语法树,然后再根据语法树生成相应的指令。语法树的末端的是一个个Token,每一步展开利用一条语法规则。
-
根据这些语法规则进一步组合可以写出更复杂的语句,比如在一条语句中完成计算、赋值和打印功能:
printf("%d:%d is %d minutes after 00:00\\n", hour, minute, total_minute = hour * 60 + minute);
理解组合(Composition)规则是理解语法规则的关键所在,正因为可以根据语法规则任意组合,我们才可以用简单的常量、变量、表达式、语句搭建出任意复杂的程序
从上面的例子可以看出,表达式不宜过度组合,否则会给阅读和调试带来困难。
根据语法规则组合出来的表达式在语义上并不一定正确,例如:
minute + 1 = hour;
等号左边的表达式要求表示一个存储位置而不是一个值,这是等号运算符和+ - * /运算符的又一个显著不同。有的表达式既可以表示一个存储位置也可以表示一个值,而有的表达式只能表示值,不能表示存储位置,例如
minute + 1
这个表达式就不能表示存储位置,放在等号左边是语义错误。表达式所表示的存储位置称为左值(lvalue)(允许放在等号左边),而以前我们所说的表达式的值也称为右值(rvalue)(只能放在等号右边)。
上面的话换一种说法就是:有的表达式既可以做左值也可以做右值,而有的表达式只能做右值。
我们看一个有意思的例子,如果定义三个变量
int a, b, c;
,表达式a = b = c
是合法的,先求b = c
的值,再把这个值赋给a
,而表达式(a = b) = c
是不合法的,先求(a = b)
的值没问题,但(a = b)
这个表达式不能再做左值了,因此放在= c
的等号左边是错的。关于整数除法运算有一点特殊之处:
hour = 11; minute = 59; printf("%d and %d hours\\n", hour, minute / 60);
执行结果是
11 and 0 hours
,也就是说59/60得0,这是因为两个int
型操作数相除的表达式仍为int
型,只能保存计算结果的整数部分,即使小数部分是0.98也要舍去。向下取整的运算称为Floor,用数学符号
⌊⌋
表示;向上取整的运算称为Ceiling,用数学符号⌈⌉
表示。例如:
⌊ 59 / 60 ⌋ = 0 ⌈ 59 / 60 ⌉ = 1 ⌊ − 59 / 60 ⌋ = − 1 ⌈ − 59 / 60 ⌉ = 0 ⌊59/60⌋=0\\\\ ⌈59/60⌉=1\\\\ ⌊-59/60⌋=-1\\\\ ⌈-59/60⌉=0 ⌊59/60⌋=0⌈59/60⌉=1⌊−59/60⌋=−1⌈−59/60⌉=0
在C语言中整数除法取的既不是Floor也不是Ceiling,无论操作数是正是负总是把小数部分截掉,在数轴上向零的方向取整(Truncate toward Zero),或者说当操作数为正的时候相当于Floor,当操作符为负的时候相当于Ceiling。回到先前的例子,要得到更精确的结果可以这样:printf("%d hours and %d percent of an hour\\n", hour, minute * 100 / 60); printf("%d and %f hours\\n", hour, minute / 60.0);
在第二个
printf
中,表达式是minute / 60.0
,60.0是double
型的,/运算符要求左右两边的操作数类型一致,而现在并不一致。C语言规定了一套隐式类型转换规则,在这里编译器自动把左边的
minute
也转成double
型来计算,整个表达式的值也是double
型的,在格式化字符串中应该用%f
转换说明与之对应。本来编程语言作为一种形式语言要求有简单而严格的规则,自动类型转换规则不仅很复杂,而且使C语言的形式看起来也不那么严格了,C语言这么设计是为了书写程序简便而做的折衷,有些事情编译器可以自动做好,程序员就不必每次都写一堆繁琐的转换代码。
习题
-
假设变量
x
和n
是两个正整数,我们知道x/n
这个表达式的结果要取Floor,例如x
是17,n
是4,则结果是4。如果希望结果取Ceiling应该怎么写表达式呢?例如x
是17,n
是4,则结果是5;x
是16,n
是4,则结果是4。(x+n-1)/n
-
🤔n为1的情况比较特殊,先不考虑
取Ceiling我首先想到了+1
n不为1的时候,如果x<n,那么x/n+1可以,x如果大于n且不是n的倍数, x/n+1也可以
但是x是n的倍数时候要想加上那个1,前面就得不能是x/n,也就是要变小一点,变小多少呢,变小0~n之间的数
这时候我们就要考虑n为1的情况了,所以x-1/n + 1
-
六.字符类型与字符编码
-
字符常量或字符型变量也可以当作整数参与运算,例如:
printf("%c\\n", 'a'+1);
执行结果是
b
-
符号在计算机内部也用数字表示,每个字符在计算机内部用一个整数表示,称为字符编码(Character Encoding),目前最常用的是ASCII码(American Standard Code for Information Interchange,美国信息交换标准码)
-
ASCII码表
-
表中每一栏的最后一列是字符,前三列分别是用十进制(Dec)、十六进制(Hx)和八进制(Oct)表示的字符编码。从十进制那一列可以看出ASCII码的取值范围是0~127。表中的很多字符是不可见字符(Non-printable Character)或空白字符(Whitespace)[3],不能像字母a这样把字符本身填在表中,而是用一个名字来描述该字符,例如CR(carriage return)、LF(NL line feed,newline)、DEL等等。
[3] 空白字符在不同的上下文中有不同的含义,在C语言中空白字符定义为空格、水平Tab、垂直Tab、换行和分页符,本书在使用“空白字符”这个词时会明确说明在当前上下文中空白字符指的是哪些字符。
二进制 Binary 八进制 Octal 十进制 Decimal 十六进制 Hexadecimal
-
-
回到刚才的例子,在ASCII码中字符
a
是97,字符b
是98。计算'a'+1
这个表达式,应该按ASCII码把'a'
当作整数值97,然后加1,得到98,然后printf
把98这个整数值当作ASCII码来解释,打印出相应的字符b
。 -
之前我们说“整型”是指
int
型,而现在我们知道char
型本质上就是整数,只不过取值范围比int
型小,所以以后我们把char
型和int
型统称为整数类型(Integer Type)或简称整型,以后我们还要学习几种类型也属于整型,将在第 1 节 “整型”详细介绍。 -
字符
'a'~'z'
、'A'~'Z'
、'0'~'9'
的ASCII码都是连续的,因此表达式'a'+25
和'z'
的值相等,'0'+9
和'9'
的值也相等。注意'0'~'9'
的ASCII码是十六进制的30~39
,和整数值0~9
是不相等的。 -
字符也可以用ASCII码转义序列表示,这种转义序列由
\\
加上1~3个八进制数字
组成,或者由\\x
或大写\\X
加上1~2个十六进制数字组成,可以用在字符常量或字符串字面值中。例如'\\0'
表示NUL字符(Null Character),'\\11'
或'\\x9'
表示Tab字符,"\\11"
或"\\x9"
表示由Tab字符组成的字符串。注意'0'
的ASCII码是48,而'\\0'
的ASCII码是0,两者是不同的。
相关资源、参考资料
-
《Linux C编程一站式学习》这书写得很不错,为什么都买不到了呢? - echo1937的回答 - 知乎 https://www.zhihu.com/question/34069391/answer/544825938
-
[大佬们的学习笔记]
以上是关于Linux C编程一站式学习笔记2的主要内容,如果未能解决你的问题,请参考以下文章