C语言点滴


title: C语言点滴
date: 2016-05-30 09:53:48
categories: C++
tags:
- C
- 编程语言

拜读赵岩老师的《C语言点滴》,目的是为了查漏补缺,在重新温习C语言基础的基础上对一些之前并未关注或者遗忘的细节给予重新定位和重视。——ycwang.2015-12-16

2.5编程风格

  • 不应该写一个很长的函数,一个函数只实现一个功能。
  • 编程字体的选择:等宽字体为佳。

常见的等宽字体

  • Monaco
  • Lucida Console
  • Lucida Sans Typewriter
  • Consolas
  • Courier New

第3章 数据类型

C语言中仅有四种数据类型,分别为整型、浮点型、指针型和聚合类型(包括数组和结构体),剩下的都是从这四种类型派生或组合而来的。——《C和指针》

接上,例如char就是一个短整型。

原码、反码和补码

  • 原码:一种计算机中对数字的二进制表示方法,数码序列中最高位为符号位,符号位为0表示正数,符号位为1表示负数。
  • 反码:如果机器数是正数,则该机器数的反码与原码一样;如果机器数是负数,则该机器数的反码为对它的原码(符号位除外)各位取反而得到。
  • 补码:如果机器数是正数,则该机器数的补码与原码相同;如果机器数是负数,则该机器数的补码是对它的原码(符号位除外)各位取反,并在末尾加1.
码 型正 数负 数
原码数字二进制表示(符号位为0)最高位符号位为1
反码同原码除符号位,对原码各位按位取反
补码同原码除符号位,对原码各位按位取反后在末尾加1

用原码表示一个有符号数会带来问题:

  • 正负相加不等于零。例如1+(-1)对应0001+1001=1010,按照原码表示等于-2.
  • 有两个零存在。分别为0000和1000。因而,原码不适合表示有符号数。

为了保证正负相加等于零。采用了反码的形式表示。但是第二个问题还没结局。因此引入补码机制。

使用补码时,也符合正负相加等于零。
故而,整型数在计算机中,使用补码表示。

3.2 整型数的溢出

C语言中int型所表示的“最大正整数”到底是多少呢?
不同的平台不同的编译器会有不同的定义。
为此,C语言在头文件 limits.h 中给出了相关的宏定义

表3.1 整型数的极限值宏定义

| 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|

3.5 int和char的关系

关于getchar()
官方的定义是:

int getchar()

Question:明明getchar返回的是一个字符,为何用int来接收?
Answer:因为getchar()读到末尾的时候,会返回EOF,而在stdio.h中,EOF被定义为-1.
例如有如下代码:

char c;//危险
while(c=getchar()!=EOF)
{
............;
}

3.9 sizeof运算符

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))

sizeof的用处

由于字节对其,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©=2
sizeof(a)/sizeof(a[0])=6

对任何类型的指针求sizeof得到的结果均是一个相同的值。C中,指针只保存一个地址,所以任何类型的指针都占用相同的字节数。
而对数组名sizeof的结果是整个数组所占的字节数。可以根据该技巧计算数组中元素的数量。sizeof(a)/sizeof(a[0])

本章完=



第四章 表达式和运算符

4.1 自增(减)运算法

C有三大运算符

  1. 算术
  2. 关系与逻辑
  3. 位操作

另外还有一些特殊的运算符,用于完成一些特殊的操作:

  1. 赋值运算符
  2. 条件运算符
  3. 指针运算符

以下以自增运算符为例说明,自减运算符同理。

对于自增运算符,只有用在表达式中前缀和后缀的区别才能有所体现。
为了说明前后椎的问题,可以将自增运算符分为两个步骤:

  1. 一个步骤是复制一份变量的拷贝用于周围的表达式。
  2. 另一个步骤是增加自身的值

前缀和后缀的区别就是:

  1. 前缀运算符首先增加变量的值,然后复制一份拷贝;
  2. 后缀则先复制一份拷贝,而后增加值。
**单目运算符都是右结合的**

4.2 左值和右值

左值得定义:
simple:能放到等号左边的就是左值。
学术定义:在内存中有一个确定位置的叫左值。

4.4 数据类型转换

  1. 隐式转换
  2. 强制转换

隐式转换的步骤:
隐式转换的步骤

强制类型转换

格式:(类型)表达式
例如:(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;
}

4.7 位运算

位运算的一个技巧:

生成一个每一位都为1的整型数,可以利用取反操作int a=~(0)。如果要生成从右边开始数,从第三位到第5位为1的一个整形数,可以通过以下步骤来完成:

  1. 首先对0按位取反,~0得到11111111.
  2. 将上述的结果左移两位,(~0)<<2, 可以得到11111100
  3. 再对上述结果按位取反,~((~0)<<2),得到00000011
  4. 对上述结果左移三位,(~((~0)<<2))<<3,得到00011000

位运算重点总结

  1. 无论是逻辑右移还是算术右移,都是对原来的结果除以2.
  2. 对于无符号数采用逻辑右移,对有符号数采用算术右移。
  3. 逻辑右移时,最高位移入0;算术右移时,最左面的符号位复制一份,插入到符号位的右面。

第五章 输入输出

5.3 单个字符输入输出

能通过键盘输入的常见的空白字符有三个,分别是
空格,由'\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时的Entergetchar读入到ch中,然后被putchar()函数输出。
由以上可以得出:getchar()函数每次只读入任意一个字符,包括回车Enter等空白字符。

5.4 字符串输入输出

gets()puts()

  • 利用gets()函数读入键盘输入的字符串时,空格和tab键都是字符串的一部分
  • gets函数读入字符串的时候,以回车或者EOF为字符串的终止符,它同时把回车从缓冲区读走。
scanf函数
#include<stdio.h>
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函数处理字符,数字和字符串
  1. scanf("%c",&c)读取一个从键盘上输入的字符时,输入中的任何字符,包括空白字符,tab字符和Enter,都不会被忽略,都会被scanf函数成功写入。这一点与getchar()函数一样
  2. 但是,当用scanf输入一个整数时,会忽略所有的空格,回车和tab。
  3. 与gets()函数不同,scanf函数输入时会忽略字符串前边的空格,tab和回车。

许多输入有问题时因为缓冲区有上次输入的剩余字符或者错误的字符。
提供一种清空缓存区的方法

int a;
do{
printf("*\n");
scanf("%d",&a);
while((c=getchar())!='\n' && c!=EOF)
	;
if(111==i)
	break;
}while(1);

使用scanf函数的时候,判断其返回值是一个比较好的方法。

5.6.6 格式化输出printf函数

与scanf的区别

  1. printf函数可以使用%f输出doublefloat两种类型,而scanf必须使用%f输入float型,而用%lf输入double型。
  2. scanf格式控制字符串中不能使用'\n'

printf格式化输出时,比较值得注意的是%后边的m.n格式,当作用于d整数,s字符串和f浮点数的时候不太一样。

  • 当作用于浮点数和字符串的时候,m代表输出至少要占m位(至少得意思是如果输出大于m位则正常输出,如果小于m位则输出m位,其余用空格填充)。n作用于浮点数时,代表小数点后边输出几位。n用作字符串时,用于输出几个字符。

例如:

float f1=123.456float f2=12.34567;

printf("%9.2f\t%9.2f\n",f1,f2);
printf("%9.2f\t%9.2f\n",f2,f1);

5.6 输入规则全真七子

  1. 键盘输入被保存于缓冲中,知道输入回车,输入函数采取缓冲区读取。
  2. getchar()每次读取任意一个字符,包括回车。
  3. gets读入字符串时,空格和tab都是字符串的一部分。gets以回车和EOF为字符串的终止符,同时把回车从缓冲区读走。
  4. scanf读入数字%d或字符串%s的时候,scanf忽略掉空格,tab,回车等空白字符。
  5. 如果缓存区不为空,scanf按照自己的格式提取。成功或失败!
  6. 掌握利用while循环清空缓存的方法,但是不要用fflush(stdin)
  7. 尝试使用fgets和sscanf.

第七章

7.1.3 宏替换

两种形式:

  • object-like
    定义:#define
    例:#define PI 3.14159
  • function-like
定义:
#define<identifier>(<replacement list>)\ 
<replacement token list>

例:#define zhouchang® (2PI®)

7.2 main函数

main函数的三种定义方式

int main()/*无传入参数*/
int main(void)/*无传入参数*/
int main(int argc,char *argv[])/*有传入参数,其中argc代表传入的参数个数,argv为一个指针数组,其中每一个指针都都指向了传入参数的具体值*/

7.4 static和const

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,函数不给出具体定义)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值