C Primer Plus (第6版) 中文版
好久没有整理读书笔记了,从今天起整理下最近几个月的读书笔记,先从C Primer Plus开始,其中大部分都是书中的只是点总结,也有一些个人的理解和扩展,书中部分内容与个人观点相左也有标记,欢迎指正和技术性留言。
**第三章 数据和C
**第四章 字符串和格式化输入/输出
**第五章 运算符、表达式和语句
**第六章 C控制语句:循环
**第七章 C控制语句:分支和跳转
**第八章 字符输入/输出和输入验证
第三章 数据和C
C语言按照计算机存储方式分为2大数据类型:整数类型和浮点类型。
char也是整数类型,存储的是整数而不是字符。char为8位但是标准ASCII码的范围是0~127.—— 扩展
位,字节,字
最小的存储单元是位(0/1),位是计算机内存的基本构建块。
字节是常用的计算机存储单元。一字节一般意义是8位(0~255的整数或一组字符)。
字是设计计算机时给定的自然存储单位。计算机字长越大,其数据转移越快,允许的内存访问也越多。
—— 扩展
浮点数和整数
二进制存储整数时,未使用的位用0填充。
浮点数分为小数和指数部分,分开存储。浮点数通常只是实际值的近似值。比如7.0被存储为浮点数6.99999。
整数都是整型常量或整形字面量。
当打印8/16进制的数时转换说明用%o或者%#o、%x或者%#x。
其他整数类型
signed是强调使用有符号类型的意图。比如short、short int 、 signed short 、signed short int 是同一种类型。unsigned是用于非负值的场合。比如unsigned int 是0~65535而不是-32768~32767。
表示的范围大小:
long long [ int ] > long [ int ] > int > short int
C语言中只有6种语句:标号语句、复合语句、表达式语句、选择语句、迭代语句、跳转语句。
个人计算机最常见的设置是long long 是64位,long 是32 位,short是16位,int是16或者32位。 —— 扩展
一些常量的表示和打印
long long类型用LL或者ll表示;U或者u表示unsigned。比如unsigned long long 表示为ull或者LLU。
打印时转换说明表示为%llu表示无符号的long long类型,%lld表示有符号的long long类型,%hd表示有符号的short类型。
溢出时,unsigned int的变量从0开始(因为无符号,只有正数和0),int是从最小的负数开始。 —— 扩展
字符常量和初始化
用单引号括起来的单个字符被称为字符常量。而双引号表示字符串。当存储的字符长度大与可存储的空间时后续的字符会自动覆盖前面的字符。
_BOOL类型
c99标准中的_BOOL类型实际上是整数类型只有1位(0或者1)。
可移植类型: stdint.h和inttypes.h
精确宽度整数类型是指定类型的宽度。比如int32_t表示整数类型的宽度是32位。
最小宽度类型是表示的的类型一定是至少有指定宽度的最小整数类型。
最快最小宽度类型是使计算达到最宽的类型集合。
float、double和long double
double和float类型的最小取值范围相同,但是必须能表示10位有效数字。
%e以指数形式打印。 —— 扩展
上溢是计算导致数字过大,超过当前类型能表达的范围时。打印时分2种情况:未定义、无穷大的特定值(inf或者infinity)。
下溢是在计算过程中损失了原末尾有效位上的数字。
_Complex 复数的实部
_Imaginary复数的虚部
I(大写的i)代替-1的平方根
类型大小
sizeof 内置运算符,以字节为单位给出指定类型的大小。sizeof(类型),而变量/常量则可以这样用:sizeof 变量/常量 ; 求得的结果是size_t 类型。转换说明为%zd(如果不支持zd可以用%lu或者%u)
类型变换
在浮点数转换为整数时,会丢弃小数部分。
刷新输出
printf()语句把输出发送到一个叫做缓冲区的中间存储区域,然后缓冲区中的内容再不断被发送到屏幕上。
从缓冲区把数据发送到屏幕或文件被称为刷新缓冲区
第四章 字符串和格式化输入/输出
c中字符串一定是以空字符结束,而且这个空字符占用字符串的容量。
单引号标识的字符是字符,而双引号则是字符串。
scanf()读取字符遇到空字符(包括空格、制表符、换行符)结束。但是会把换行符保留在输入队列(缓冲区中),下一次读取缓冲区会读到换行符。
strlen()函数
计算的是字符长度(包括标点和空格符号但是不包括结束符)而不是sizeof那样的字节长度(包括结束符)。
宏定义(也成为明示常量)在编译时替换,预处理器会在编译时直接将宏替换为值。
const限定符
变量被const限定后,变量变为只读。但是仍然是变量。
printf()函数
printf( 格式字符串( 包含 字面字符,转换说明) , 待打印项1,待打印项2);
转换说明仅仅是一个翻译说明,将存储的值按照给定的进制/格式翻译并打印出来。
int main (int argc , int argv[])
{
int a= 0;
printf("a = %d " , a);
// 这里"a = %d"就是格式字符串,a = 是字面字符,%d是转换说明,a是待打印项
return 0;
}
%g 根据值的不同自动选择%f或%e
%G 根据值的不同自动选择%f或%E
%p 指针
%u 无符号10进制整数
%x 无符号16进制整数
%X 无符号16进制整数
printf()的修饰符
修饰符 | 含义 |
---|---|
标记 |
|
数字 | 最小字段宽度 |
.数字 | 精度 |
hh | 表示unsigned char / signed char |
j | 表示intmax_t 或者uintmax_t类型的值,这些类型在stdint.h中 |
t | 表示ptrdiff_t类型的值,表示2个指针的差值 |
z | 表示size_t类型的值 |
在stdio.h把size_t定义为sizeof的返回类型这成为底层类型。
在printf()中所有float类型的数据自动转换为double,也就是double和float在printf()中是一种类型都是double。
printf()中的标记
标记 含义 - 左对齐 + 显示符号正数为+负数为- 空格 正数为空格 负数为- # 8和16进制在输出前面加0和0x;浮点数可以保证小数点打印即使小数点后没有数字用0填充 0 0填充字段宽度
函数参数传递在C中是把参数的值放入栈中,计算机根据变量类型(声明参数时的变量类型)放入栈中(在栈中分配空间按照参数类型),当取数据时按照转换说明取值。这里需要注意字节大小。比如long类型是8字节,但是取值时按照%d的话,就只能取4字节,所以相当于只取了一部分。
使用scanf()
scanf()读取基本变量时需要&但是把字符放入字符数组时不用。
scanf()的转换说明的修饰符
转换说明 | 含义 |
---|---|
* | 抑制赋值(跳过赋值) |
数字 | 最大字段宽度 |
scanf("%*d %*d %d ", &c); // 会忽略前面2个输入,把第三个输入给变量c
值得注意的是scanf()读取字符会一个一个的保存,当遇到非法字符时会停在哪,并会把这个非法字符保存起来但是并不会赋给变量。当你下次继续读取字符时,读取到的仍然是上次保存的那个非法字符,所以不会读取成功程序会暂停在这。而且当把字符串放入数组中时,scanf会自动在末尾加上‘\0’作为字符串的结束,并占用一个空间。
scanf函数返回读取的项数(有几个转换说明就是几项)。在文件操作中,文件末尾会返货EOF(-1)。
第五章 运算符、表达式和语句
几个术语:左值、数据对象、右值和运算符
数据对象: 用于存储值的数据存储区域
左值:指定一个对象引用内存中的地址,可用在赋值运算符的左侧(=)
右值:本身不是左值并且可以赋给可修改的左值的量
整数除法会丢弃小数部分,称为趋零截断
c语言中的求模运算符只能用于整数不能用于浮点数。a% b 相当于 a- (a/b)*b
递增运算符
前缀模式:++a
后缀模式: a++
递增运算符生成的机器代码效率更高。
如果出现a = a +1 ,别人会怀疑你是否是c程序员
—— 扩展
递增和递减运算符只能作用一个变量而不是表达式。比如:(y+x++)*z相当于(y+x)*z;x++;
为了避免出现递增和递减的混乱:
如果一个变量出现在一个函数的多个参数(或者多次出现在一个表达式)中,不要对变量使用递增或者递减。
c语言中没有赋值语句和函数调用语句,因为都是表达式语句。 —— 扩展
C语言中的副作用和序列点
副作用是对数据对象或文件的修改。
序列点是程序执行的点,在这个点上,所有的副作用都在进入下一步之前完成。(逗号也是一个序列点,逗号的左侧项的所有副作用都会在逗号执行前发生)
完整表达式指的是这个表达式不是其他表达式的子表达式。
类型转换
升级:较小类型转换为较大类型
降级:较大类型转换为较小类型
强制类型转化运算符:圆括号和它括起来的类型名
函数原型:也就是函数的声明, 描述了函数的返回值和参数。
第六章 C控制语句:循环
当比较浮点数时,最好不要使用==而使用<或者>,因为浮点数并不是完全精确的。也可以用fabs()函数可以方便的比较浮点数。
c语言中0是false,其他都是true。
for循环作为最灵活的循环:可以随意设置偏移量,而且省略了for的第二个表达式则视为真,也就是说循环会一直进行。而且for中也是可以多种多样的形式。比如for(printf(” “) ;num ! = 6 ; sacnf(” %d “, &a) )
入口循环和出口循环
while( ) 、for()等在进入前需要条件判断的都是入口循环;而do{}while()则是出口循环;出口循环比入口循环都执行一次!
数组
C语言中数组没有越界检查:也就数说int arr [5];使用了arr[6]也不会有任何问题,但是很明显arr[6]元素占用了其他程序的内存空间,其结果是未知的。
访问数组的元素和访问变量一直但都与访问数组不同。
**第七章 C控制语句:分支和跳转
getchar()与putchar()
getchar()与putchar()只处理字符所以不需要转换说明,而且效率比scanf和printf快。
getchar返回的是int类型,如果赋值给char类型,部分编译器会有warning!
// isalpha()函数在ctype.h中,如果参数是一个字母就返回非零值,在书中此处有错误,isalpha函数是验证字母而不是字符
char c ;
c = getchar();
if(!isalpha(c)){
printf("这不是一个字母");
}else{
putchar(c);
}
ctype.h中类似的验证的函数还有:
函数名 | 参数 |
---|---|
isalnum() | 字母数字为真 |
isalpha() | 字母为真 |
isblank() | 空白字符为真 |
iscntrl() | 控制字符为真 |
isdigit() | 数字为真 |
C99有一个标准:编译器至少要支持127层套嵌判断。 —— 扩展
// 求一个数的约数
void get_divisor( const int divisor )
{
for( int div = 2 ; ( div * div ) <= divisor ; div++ )
{
if( divisor % div == 0 )
{
if(div* div != divisor )
printf(" 约数有 %d %d ", div , divisor/div);
else
printf("约数有 %d ", div);
}
}
}
// 根据上面的启发可以想到求一个数是不是素数的函数
int isprime(const int divisor)
{
isPrime = 1;
if( divisor == 1 ){
return isPrime;
}
for( int div =2 ; ( div * div ) <= divisor; div++ )
{
if( divisor % div == 0 )
{
isPrime = 0
}
}
return isPrime;
}
iso646.h头文件
逻辑运算符的备选拼写:
&& => and
|| => or
! => not
—— 扩展
比如 x!=0 && (20 /x)< 5 类似这样的表达式只有x不等于0才会计算后面的表达式,如果x为0程序不会计算后面打表达式,这种写法在js中非常常见。
跳出嵌套循环时break只会跳出当前的循环,而且c语言避免使用goto;但是在TCP等网络协议的源码和实现中goto的使用非常频繁。 —— 扩展
**第八章 字符输入/输出和输入验证
缓冲区
回显用户输入的字符后立即重复打印该字符是属于无缓冲输入。
而对于那些需要用户按下enter键后才前不会重复打印刚输入的字符属于缓冲输入。
回显输入:用户输入的字符直接显示在屏幕上;
无回显输入:击键后对应的字符不显示。
缓冲分为完全缓冲I/O和行缓冲I/O。
完全缓冲输入指的是当缓冲区被填满时才刷新缓冲区(内容被发送至目的地),比较常出现在文件中。
行缓冲I/O指的是出现换行符时刷新缓冲区。
回显无缓冲输入函数:getche()
无回显无缓冲输入函数:getch()
文件、流
底层I/O:直接调用操作系统的函数。
文件结尾的标志EOF(在stdio.h中 #define EOF (-1) )
为什么是-1呢?因为标准字符集是0~127(扩展字符集是0~255),都不包含-1所以-1用来标记结尾。 —— 扩展
重定向
< 与> 重定向运算符
a < b 把b中字符导入a中;>则是相反。
但是在UNIX、Linux和 Windows/DOS系统中使用重定向运算符需要遵循下面的规则:
1 重定向运算符连接一个可执行程序和一个数据文件
2 重定向运算符不能读取多个文件的输入,也不能把输出定向至多个文件
3 文件名和运算符之间不能有空格,除非空格代表了特殊含义
还有>>运算符;该运算符可以把数据添加到现有文件末尾。|运算符能把一个文件的输出连接到另一个文件的输入。