拜读赵岩老师的《C语言点滴》,目的是为了查漏补缺,在重新温习C语言基础的基础上对一些之前并未关注或者遗忘的细节给予重新定位和重视。——ycwang.2015-12-16
常见的等宽字体Monaco Lucida Console Lucida Sans Typewriter Consolas Courier New
C语言中仅有四种数据类型,分别为整型、浮点型、指针型和聚合类型(包括数组和结构体),剩下的都是从这四种类型派生或组合而来的。——《C和指针》
接上,例如char就是一个短整型。
- 原码:一种计算机中对数字的二进制表示方法,数码序列中最高位为符号位,符号位为0表示正数,符号位为1表示负数。
- 反码:如果机器数是正数,则该机器数的反码与原码一样;如果机器数是负数,则该机器数的反码为对它的原码(符号位除外)各位取反而得到。
- 补码:如果机器数是正数,则该机器数的补码与原码相同;如果机器数是负数,则该机器数的补码是对它的原码(符号位除外)各位取反,并在末尾加1.
码 型 | 正 数 | 负 数 |
---|---|---|
原码 | 数字二进制表示(符号位为0) | 最高位符号位为1 |
反码 | 同原码 | 除符号位,对原码各位按位取反 |
补码 | 同原码 | 除符号位,对原码各位按位取反后在末尾加1 |
用原码表示一个有符号数会带来问题:
- 正负相加不等于零。例如1+(-1)对应0001+1001=1010,按照原码表示等于-2.
- 有两个零存在。分别为0000和1000。因而,原码不适合表示有符号数。
为了保证正负相加等于零。采用了反码的形式表示。但是第二个问题还没结局。因此引入补码机制。
使用补码时,也符合正负相加等于零。
故而,整型数在计算机中,使用补码表示。
C语言中int型所表示的“最大正整数”到底是多少呢?
不同的平台不同的编译器会有不同的定义。
为此,C语言在头文件 limits.h 中给出了相关的宏定义
char | int | short | long | long long |
---|---|---|---|---|
SCHAR_MAX | INT_MAX | SHRT_MAX | LONG_MAX | LLONG_MAX |
SCHAR_MIN | INT_MIN | SHRT_MIN | LONG_MIN | LLONG_MIN |
UCHAR_MAX | UINT_MAX | USHRT_MAX | ULONG_MAX | ULLONG_MAX |
CHAR_MIN | ||||
CHAR_MAX |
关于getchar()
官方的定义是:
int getchar()
Question
:明明getchar返回的是一个字符,为何用int来接收?
Answer
:因为getchar()读到末尾的时候,会返回EOF,而在stdio.h中,EOF被定义为-1.
例如有如下代码:
char c;/*危险*/
while(c=getchar()!=EOF)
{
............;
}
sizeof是C中的一个关键字,不是函数,但是其行为却类似于一个函数,因为其返回一个类型为size_t
的无符号的整型数。
size_t
其实就是一个无符号的整型数。因为任何一个类型的size
都不应该是负数。
对于无符号数的减法,必须要保证被减数大于减数。如下的例子就是无符号的溢出带来的,其打印出的结果是str2 is longer than str1
.
char str1[]='abcdefg';
char str2[]='abc';
if(strlen(str2)-strlen(str1)>0)
printf("str2 is longer than str1")
这里正确的比较方法应该是:if(strlen(str2)>strlen(str1))
由于字节对其,C中struct的长度并不等于每个单个成员变量的数据类型的长度和。
所以当我们使用二进制来读写文件的时候,通常用sizeof
来计算变量所占内存的真是尺寸。
例如:
struct student{
int num;
char name[100];
}stu1;
fwrite((char*)stu1,sizeof(stu1),1,fp);
例,数组和指针在sizeof上的区别:
int a[]={0,1,2,3,4,5};
char c[]={'a','b'};
int *ip=a;
char *cp=c;
printf("sizeof(ip)=%d\n",sizeof(ip));
printf("sizeof(cp)=%d\n",sizeof(cp));
printf("sizeof(a)=%d\n",sizeof(a));
printf("sizeof(c)=%d\n",sizeof(c));
printf("sizeof(a)/sizeof(a[0])=%d\n",sizeof(a)/sizeof(a[0]));
输出结果为:
sizeof(ip)=4
sizeof(cp)=4
sizeof(a)=24
sizeof(c)=2
sizeof(a)/sizeof(a[0])=6
对任何类型的指针求sizeof得到的结果均是一个相同的值。C中,指针只保存一个地址,所以任何类型的指针都占用相同的字节数。
而对数组名sizeof的结果是整个数组所占的字节数。可以根据该技巧计算数组中元素的数量。sizeof(a)/sizeof(a[0])
==============本章完===============
C有三大运算符
1. 算术
2. 关系与逻辑
3. 位操作
另外还有一些特殊的运算符,用于完成一些特殊的操作:
1. 赋值运算符
2. 条件运算符
3. 指针运算符
以下以自增运算符为例说明,自减运算符同理。
对于自增运算符,只有用在表达式中前缀和后缀的区别才能有所体现。
为了说明前后椎的问题,可以将自增运算符分为两个步骤:
1. 一个步骤是复制一份变量的拷贝用于周围的表达式。
2. 另一个步骤是增加自身的值
前缀和后缀的区别就是:
1. 前缀运算符首先增加变量的值,然后复制一份拷贝;
2. 后缀则先复制一份拷贝,而后增加值。
左值得定义:
simple:能放到等号左边的就是左值。
学术定义:在内存中有一个确定位置的叫左值。
隐式转换的步骤:
格式:(类型)表达式
例如:(float)i
逻辑运算符中注意的问题:
&&
与||
均为从左向右的顺序进行。
在&&
中,逻辑假与任何逻辑值&&
都是假,所以&&
右边的操作根本不执行,该行为称为逻辑运算符中的短路效应。同理,在||
中也存在短路效应。
例:
if(5<3 && i++);
if(5>3 || i++);
在上式两行代码中,&&
和||
右边的操作根本不!执!行!
除了上述三个固定顺序的运算符(&&,||,?:
)运算符外,其他的运算符号是没有计算顺序的,例如:
int m;
printf("%d%d",m,m=7)
谁能知道上边的这个代码竟然特么的会输出7 7
,而不是我们希望看到的1 7
。
功能:
- 判断整除。通过a%b==0判断a是否能被b整除。
- 映射到某个范围。给定任何一个数a,都可以通过a%100+1
来把a映射到[1,100]的区间。例如,想模拟出扑克牌的过程,即可以通过rand()%54+1
来模拟。
- 得到后n位数。例如a%1000可以得到a的最后三位数。
- 分别得到每一位上的值。运用上述思想拓展可得。
例如:
int a=1234;
while(a)
{
printf("%d\n",a%10);
a=a/10;
}
又如,打印出整数的二进制表达式:
int a=1234;
while(a)
{
printf("%d\n",a%2);
a=a/2;
}
位运算的一个技巧:
生成一个每一位都为1的整型数,可以利用取反操作
int a=~(0)
。如果要生成从右边开始数,从第三位到第5位为1的一个整形数,可以通过以下步骤来完成:
1. 首先对0按位取反,~0
得到11111111
.
2. 将上述的结果左移两位,(~0)<<2
, 可以得到11111100
3. 再对上述结果按位取反,~((~0)<<2)
,得到00000011
4. 对上述结果左移三位,(~((~0)<<2))<<3
,得到00011000
位运算重点总结
- 无论是逻辑右移还是算术右移,都是对原来的结果除以2.
- 对于无符号数采用逻辑右移,对有符号数采用算术右移。
- 逻辑右移时,最高位移入0;算术右移时,最左面的符号位复制一份,插入到符号位的右面。
能通过键盘输入的常见的空白字符有三个,分别是
空格
,由'\t'
表示的tab键和由'\n'
表示的Enter键。
int getchar()
该函数顾名思义,即为从流中取出一个字符,默认从stdin中输入流中得到一个字符,也就是说从键盘输入中获得一个字符,与之相对于的输出函数是putchar()
函数.
例如,有如下程序:
int ch;
/*first*/
ch=getchar();
putchar('1');
putchar(ch);
putchar('\n');
/*second*/
ch=getchar();
putchar('2');
putchar(ch);
putchar('\n');
当输入ab+Enter
,其中+Enter
表示回车。
此时屏幕上输出为:
ab
1a
2b
请按任意键继续…
当输入a+Enter时,其中+Enter
表示回车。
此时屏幕上输出为:
a
1a
2
.
.
请按任意键继续…
注意第二次的输出,请按任意键继续
上边有两行空格。
此时,由于数字2的后边分别输入了两个回车。输入a+Enter
时的Enter
被getchar
读入到ch中,然后被putchar()
函数输出。
由以上可以得出:getchar()函数每次只读入任意一个字符,包括回车Enter
等空白字符。
gets()
和puts()
- 利用gets()函数读入键盘输入的字符串时,空格和tab键都是字符串的一部分
- gets函数读入字符串的时候,以回车或者EOF为字符串的终止符,它同时把回车从缓冲区读走。
#include
int main()
{
char str[5];
char c;
short int i;
int result;
result=scanf("%c,%hd %5s",&c,&i,str);
return 0;
}
对于上述程序,如果输入a,133 abc
,此时a被保存于c中,133保存于i中,abc保存于字符型数组str中。因为有三个变量被成功写入,因此scanf返回值为3.
scanf函数会忽略
stdin
中的空白字符,即空格和tab都会被忽略掉。
scanf函数的实参是变量的地址。
scanf("%c",&c)
读取一个从键盘上输入的字符时,输入中的任何字符,包括空白字符,tab字符和Enter,都不会被忽略,都会被scanf函数成功写入。这一点与getchar()函数一样许多输入有问题时因为缓冲区有上次输入的剩余字符或者错误的字符。
提供一种清空缓存区的方法
int a;
do{
printf("*\n");
scanf("%d",&a);
while((c=getchar())!='\n' && c!=EOF)
;
if(111==i)
break;
}while(1);
使用scanf函数的时候,判断其返回值是一个比较好的方法。
与scanf的区别
1. printf
函数可以使用%f
输出double
和float
两种类型,而scanf
必须使用%f
输入float型,而用%lf
输入double型。
2. scanf格式控制字符串中不能使用'\n'
printf格式化输出时,比较值得注意的是%后边的m.n格式,当作用于d整数,s字符串和f浮点数的时候不太一样。
- 当作用于浮点数和字符串的时候,m代表输出至少要占m位(至少得意思是如果输出大于m位则正常输出,如果小于m位则输出m位,其余用空格填充)。n作用于浮点数时,代表小数点后边输出几位。n用作字符串时,用于输出几个字符。
例如:
float f1=123.456;
float f2=12.34567;
printf("%9.2f\t%9.2f\n",f1,f2);
printf("%9.2f\t%9.2f\n",f2,f1);
- 键盘输入被保存于缓冲中,知道输入回车,输入函数采取缓冲区读取。
- getchar()每次读取任意一个字符,包括回车。
- gets读入字符串时,空格和tab都是字符串的一部分。gets以回车和EOF为字符串的终止符,同时把回车从缓冲区读走。
- scanf读入数字%d或字符串%s的时候,scanf忽略掉空格,tab,回车等空白字符。
- 如果缓存区不为空,scanf按照自己的格式提取。成功或失败!
- 掌握利用while循环清空缓存的方法,但是不要用fflush(stdin)
- 尝试使用fgets和sscanf.
两种形式:
- object-like
定义:#define
例:#define PI 3.14159
- function-like
定义:
#define<identifier>(<replacement list>)\
<replacement token list>
例:#define zhouchang(r) (2*PI*(r))
main函数的三种定义方式
int main()/*无传入参数*/
int main(void)/*无传入参数*/
int main(int argc,char *argv[])/*有传入参数,其中argc代表传入的参数个数,argv为一个指针数组,其中每一个指针都都指向了传入参数的具体值*/
const char *p 和 char *const p
的辨析
《Effictive C++》条款21中提供了一种方法,画一条垂直线穿过指针声明中的星号*。
如果const出现在直线的左边,表示指针指向的数据为常量
如果const出现在右边,表示指针本身为常量,不能改变指针p的指向。
static
两大特性:静态和隐藏
static的用法
static和全局变量一样,都被存储在内存区的静态存储区。
static类型的变量,如果不予初始化,其初始值为0,而不像某些自动变量那样是一个随机数。
static比较经典的一个用法就是统计函数被调用了多少次。
static的第二个特性
信息隐藏:static变量只在定义它的范围内可见,在其他范围内不可见。
如果static用来修饰函数,这个函数只在当前的.c文件中可见。这样我们就可以在不同的.c文件中定义同名函数,而不会引起冲突,只要其中一个函数被static修饰。
如果要定义一个能被其他.c文件使用的全局变量,可以在任一个.c文件中定义这个全局变量,定义的时候不能用static修饰。在其他.c文件中使用该变量时,需要加上关键字extern对其进行修饰,表示该变量已经在其他.c文件中被定义。
表7.1 static用法总结
变量类型 | 声明位置 | 储存位置 | 作用域 | 作用 |
---|---|---|---|---|
static全局变量 | 语句块之外 | 静态存储区 | 整个文件 | 不允许其他文件访问 |
static局部变量 | 语句块中 | 静态储存区 | 整个语句块 | 值在程序执行时一直保持: |
extern
单独讲解
extern可置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量或函数时,在其它模块中寻找其定义。另外,extern也可用来进行链接指定。
关于头文件包含
当有多个.c文件同时使用同一个函数时,在每一个文件中申明函数显得累赘,因此此时可以将函数的声明放到一个头文件中,然后每一个使用该函数的.c文件用include包含进申明了该函数的头文件便可。这样写的好处,一是看着代码不累赘;二是当函数需要发生变化时不用每个c文件都做修改,只需要在包含函数的c文件中做修改即可。
再三说明:头文件中只应该包含那些不申请内存的声明语句
一般来说,头文件中应该包含的内容有如下几类
1.避免重复包含的#ifndef…#endif语句
2.宏定义
3.struct,union和enum等类型的typedef类型的定义
4.全局变量以及不申请内存的声明(变量前加extern,函数不给出具体定义)