了解&基本概念
- 我这本书的C版本是C89。
- 要从逻辑上删除一段C代码,更好的方法是使用 #if 指令。
#if 0
statements
#endif
- 引用:传址调用,也就是数组参数。标量和常量则是按值传递的。
- C中约定,字符串就是一串以 NUL 字节结尾的字符。NUL作为字符串终止符,并不被看作整个字符串的一部分。
- NUL和NULL
NUL = ‘\0’,NULL是指一个其值为0的指针。都是整型值,值是相同的。但是所代表的意义不同,因此需要在合适的场合使用合适的常量。
NUL需要自行定义,NULL是头文件stdio中定义的。
- scanf 函数的返回值是函数成功转换并存储于参数中的值的个数。
- EOF表示文件的结尾
- C可以把赋值操作蕴含于while语句内部,这样就允许程序员消除冗余语句。
- 一个函数的形参变量被声明为const
在这个函数中,这个参量不能被修改。
编译器会自动去验证这个意图。
function(const int value){
statement
}
当const出现如上的情况时,在程序运行时,function函数中传入的是参数的值(数值),并不像正常的常量一样保存在常量区内。
- 使用数组的时候,一定要注意数组的溢出
比如在条件判断语句中用&&加上溢出判断
3.数据
- 变量的三个属性:作用域;链接属性;存储类型
这三个属性决定变量的“可视性”(变量可以在什么地方使用),“生命期”(变量的值能够保存多久)。
基本数据类型:整型、浮点、指针、聚合类型(结构体和数组)
整型
C文件:limits.h
整型的类型和范围
C89引入了新标准,也就是各个整型值的最小范围,这种方式有助于C程序在不同的平台之间的移植。
类型 | 最小范围 |
char | 0~127 |
signed char | -127~127 |
unsigned char | 0~255 |
short int | -32767~32767 |
unsigned short int | 0~65536 |
int | -32767~32767 |
unsigned int | 0~65536 |
long int | -2147483647~2147483647 |
unsigned long int | 0~4294967295 |
必须满足最小范围,short整型最少16位,long整型最小32位,至于int需要看编译器的规定。
最大范围并没有限制,因此也有三个整型都是同样大小,例如long、short和int都是32位的情况,这些都要依据使用的编译器和平台。
对于char类型来说,不同的编译器可能会分配给char不同的大小,有的是有符号,有的是无符号。
这就会产生可移植问题,统一将char声明为unsigned或者signed都不是很好的方式。
当可移植问题比较重要的时候,我们需要将char变量的范围限制在有符号和无符号之间,例如ASCII的字符都是在这个范围内的。
整型字面值
- 整型字面值就是程序中出现的各种直接数值,例如1,0,300这类的数字,这些数字指定了自身的数值,并且无法改变。
- 可以在整形字面值的后面添加后缀来指定其整型类型,例如后缀L就是long,后缀u就是unsigned。
- 添加前缀能够标注整型字面值的数制类型,也就是八进制、十进制、十六进制。
八进制以0开头,十六进制以0x开头。
字符常量
字符常量有一个前缀L时,表示的是宽字符常量。宽字符常量不是很理解,网上说是用于编码之间的转换,unicode和ascii这两个码,微软关于C宽字符常量的使用方式如下。
以前缀开头的字符文本
L
是宽字符文本。包含单个字符、转义序列或通用字符名称的宽字符文本的值的值 等于其编码在执行宽字符集中的数值,除非字符文本在执行宽字符集中没有表示形式,在这种情况下,该值是实现定义的。
包含多个字符、转义序列或通用字符名称的宽字符文本的值是实现定义的。 有关 MSVC,请参阅下面的Microsoft 特定部分。
👇
宽字符串文本是一个以 null 结尾的常数数组,其
wchar_t
前缀为 "L
",包含除双引号("
)、反斜杠(\
)或换行符以外的任何图形字符。宽字符串文本可包含上面列出的转义序列和任何通用字符名称。
枚举类型
枚举默认按顺序赋值,这些都是整型值,第一个的值为0,第二个是1,以此类推。
字符串常量
- 在C中没有字符串的概念,一串字符串的本质是以NUL字节结尾的零个或多个字符。
- 字符串通常存储在字符数组里面
- 字符串都是以NUL结尾的,NUL并不是一个可打印的字符。
typedef
- C语言支持typedef这个机制,可以为各种数据类型定义新名字。
- #define和typedef是不一样的,在使用的时候应当注意,#define是一种预处理命令,在预处理阶段将#define的内容进行替代,无法合理处理数据类型。
常量
定义常量有两种形式:const关键字和#define。
- const将一个变量变成“不能被修改”的变量,被用于只允许使用变量的地方。
- #define是一种创建名字常量的机制,用于具有特殊含义的整形字面值。
变量的三个属性:链接属性、作用域、存储类型
作用域
程序中标识符能够被使用的区域,作用域要看变量的声明位置。那么在该变量的作用域之外的同名变量,并不影响该变量的使用。
- 代码块作用域
在代码块内部声明的变量,仅限于在代码块内部使用,作用域就是从声明处开始,代码块结束位置结束。
- 文件作用域
声明在所有代码块之外的标识符具有文件作用域,从文件开始到文件结束都能够被访问。
- 原型作用域
适用于在函数里声明的参数,作用范围在这个函数内。
- 函数作用域
链接属性
处理在不同文件中出现的标识符,注意,链接属性和作用域有关,但并不等于作用域。
external(外部):无论标识符声明多少次,位于几个源文件,都表示一个实体。
internal(内部):在同一个文件中,无论标识符声明多少次,都表示一个实体。
none(无):被作为一个单独的实体,每次声明都指向不同的实体。
- 关键字extern和static用于修改标识符的链接属性。extern能将internal转换成external,static能将external转换成internal。
- 文件内的全局变量的链接属性为internal,同理代码块的变量也是这样。作用域只有自己的区域。
- 函数的链接属性为external,因此外部是可以调用的。
- 链接属性标识符是一次性的,第一次声明之后,后面第二次以及以上都不修改第一次的链接属性。
存储类型
存储变量值的内存类型,决定变量在内存中何时创建、何时销毁以及它的值保存多久。
- 普通内存
- 运行时堆栈
- 硬件寄存器
详细整理👉[程序运行以及内存管理]
关键字:auto、static、register
代码块内部声明的变量,存储在堆栈之中,称为自动变量,关键字auto。
static:
函数定义:用于修改标识符的链接属性,但是标识符的存储类型和作用域不受影响
代码块内部的变量声明:修改变量的存储类型,但是变量的链接属性和作用域不受影响。
这三个属性描述一个变量在代码内、可执行文件内、内存内的一些情况。作用域用于描述该变量使用范围,链接属性用于说明变量与其他文件中标识符的关系,存储类型用于说明变量被保存的地方。
前面说过,变量的这三个属性决定变量的可视性和生命期。
👉在不同文件中具有相同名称的变量,是同一个变量么?
在变量的声明代码块内,作用域以及变量的链接属性。
首先要看这两个变量的声明,来确定变量的三个属性。声明的位置确定变量的作用区域,链接属性确定能否在不同的文件中使用,变量的类型确定在内存中占据的大小。这三者共同决定变量在内存中的位置(堆栈、静态存储区)。
根据这三个属性,就能知道它的作用范围和生命周期。
4.语句
空语句:只包含一个分号。用于语法要求出现一条完整的语句,但并不需要做执行任何人物的时候。
表达式语句:C中没有“赋值语句”,而是将赋值操作符结合表达式,给变量赋值。赋值就像是加法减法一样,是一种操作。
赋值语句会利用函数的‘副作用’。
一个函数执行主要的功能,同时会产生一些其他的影响,这些其他的东西就是副作用。
例如 printf 打印字符串的时候,会返回一些校验值,但是我们只关心它打印字符串的功能,并不关心其返回值。
但是对于getchar()这个函数,主要的功能是读取一个输入的字符串,如果我们没有去读取getchar的返回值,那么getchar读取到字符后就丢弃它。
- 判断语句 if 中要注意,C并不具备布尔类型,而是用整型来代替。因此 if 语句判断的时候,0值表示假,非零值表示真。如果你想准确的判断真假,应该用更加精确的0和1来判断。
- while循环中,判断条件可以加在 if 语句中,也可以用while来判断,要注意条件一定要正确。
- for语句包含初始化、条件和调整部分。continue关键字直接将本轮循环转换到调整部分。
- switch语句有一些限制:
switch后面判定的值一定要是整型
case标签所具有的值一定要是常量
如果case没有break,那么执行流将贯穿整个switch代码块。
实际上,你可以根据实际需要选择是否让switch执行流贯穿代码块,但是这种情况十分罕见。
C不具备任何输入输出语句,是通过IO调用库函数来实现的。
C也不具备任何异常处理语句,它们也是通过调用库函数来实现的。
5.操作符和表达式
(按照操作符的优先级顺序)
- 算数运算符中,除%之外,其他的运算符既适用于整数又适用于浮点
- 移位操作符中,右移位操作面临一个补充的问题:逻辑移位和算数移位。逻辑移位就是简单直白的用0填充右移过程中的空位。算术移位使用符号位填充空位,不改变数值的正负。
- 对于运算符的使用,需要参考你用的编译器。
- 赋值运算符返回的值是左操作数的新值。
y = (x = 4+5):这里,x为9,同时(x = 4+5)返回的值为9,因此 y 也等于9。
- 逻辑运算符:利用操作符优先级进行“短路求值”,可以节省时间,经典例子->Pascal。
- 逗号操作符,用于将多个表达式分开,从左向右逐个求值。一整个逗号表达式的值为最后的一个表达式的值。
- 编写C程序的时候,要注意语句的执行顺序和逻辑。
参考资料:
《C与指针》
https://www.cnblogs.com/gatsby123/p/11150472.html
https://docs.microsoft.com/zh-cn/cpp/cpp/string-and-character-literals-cpp?view=msvc-160