先尝试回答一下以下问题:

  1. C语言的数据类型有哪些?基本数据类型可以怎样分类?
  2. int可以表示的整数的数量级?char默认有符号还是无符号?为什么下面代码的c=26 (%d)?
    1
    char a=175; char b=107; char c=a+b;
  3. 几种算数运算符对整数、浮点数分别是怎么处理的?结果是整数还是浮点数?
  4. 奇葩的逗号表达式是怎么回事?
  5. scanf_s被报错了是怎么回事?格式控制字符串怎么理解?

基本数据类型

C语言的数据类型包括基本类型、构造类型、指针类型和空类型。构造类型包括数组、结构、联合和枚举,是用户以基本类型为基础构造的复杂类型。下面细说基本数据类型。

C语言的基本类型可以按照如下方式分类:

值得一提的是char被划分在整型数据里面,其实这是很合理的:字符型变量真正存储的是一串8位的数码,而当编译器知道这个变量是字符型后,就会通过该编码找到对应的字符。所以char可以当作1byte的整数使用;同理,int也可以当作char使用,如下:

1
int a=65; printf("%c\n",a);

最后会打印A,因为在格式说明里已经要求把int按照char输出了。但这种用法除了学习价值外就毫无意义了。

我补充一下自己的意图吧。首先,我们知道汉字的GBK编码需要2个字节的内存,显然8位的char是无法存放的,所以才有printf("%c%c%c%c%c%c\n",206,210,176,174,196,227);的用法。于是我用2个字节的short int去存储GBK编码,然后用scanf输出汉字,但最后失败了。因为格式控制字符串里面对%c对数的范围也有要求,即不大于255。32位的整数溢出了,所以最后什么都没有输出?因为负数没有ASCII编码!


int就比较有趣了。早期的C语言,随着硬件的发展,int的大小也发生着改变。操作系统16位时,int的大小为2字节;操作系统32位时,int的大小为4字节;然而今天,操作系统已经64位,int的大小仍为4字节——其原因是C语言在32位时期已经相当普及,如果直接变成64位则会引发极大的问题,包括很多社会问题!C语言妥协的做法,就是定义了long long int。

我们不妨先计算一下整型数据的数量级。char: 2^8=256(百); short: 2^16=65536(万);long: 2^32=4294967296(十亿);long long: 2^64(天文数字!)。若为有符号数则正负各占一半(补码0视为正数)。

现在的运算,光做加减法,int为32位都可能会溢出,更别说乘除了。而64位完全不存在这个问题。所以long long是有必要的。

我们写程序时,应该要仔细核算,在保证不越界的前提下,选择合适的整型大小,以节省空间——这是作为程序员的职责之一。


C语言的char是默认有符号的,即MSB填0。这也是ASCII码区别其他编码的特点,因为其他大多数编码都是兼容ASCII码的,自然要取得比ASCII码更大,即MSB为1。编译器可以通过这个特点区分ASCII码和其他编码。

关于下面c的结果为26,其实就是溢出造成的。(这更容易体会到低位是多么容易溢出!)

1
char a=175; char b=107; char c=a+b;

175+107=282,282-256=26,所以结果为26,真的是这样吗?

前面说过,char默认有符号,所以175就已经越界了!大于127就算越界了!所以应该是107-81=26。


不知你是否见过这样的用法:

1
2
3
4
typedef unsigned char uint8;
typedef unsigned short unit16;
typedef unsigned long uint32;
typedef unsigned long long uint64;

虽然是换汤不换药的做法,但是能实现用户自定义类型,极大提高程序的兼容性。而且老师还叮嘱过我们,以后看到这样的代码,一定要去精读——这绝逼是好代码。

基本运算

双目运算中的算术运算应该是我们最熟悉的了,同时它也包括了自动类型转换,这点值得我们注意。

  1. 两个数均为整数,+,-,*,/后的结果也为整数。不过两数相除的结果可能是小数,C语言一律向下取整。即3/5=0。如果想要向上取整,规范的做法是:

    1
    w/64+!!(w%64)

    这里连续使用两次“!”的技巧性很强。实际上,C语言后来还定义了向下取整函数floor和ceil,但是floor基本没人用,因为C语言自动会向下取整所以何必呢?ceil用得也很少,因为w/64+!!(w%64)的做法已经成为向上取整约定俗成的方式了,性能也很好。

  2. %的两个数只能是整数。

  3. +,-,*,/的两个数中一个为浮点数,结果都自动转换为double型。这样可以提高运算精度。这也是类型转换中自动类型转换的经典例子。

逗号经常作为变量表,形参表等列表元素之间的分隔符,这样用逗号是没问题的。但是,当看到逗号运算符时,却往往脑壳疼,因为这样的语句可读性是很低的。自己写程序时不应该用,但是别人用了要读得懂。

逗号表达式的意思是从左至右依次计算各表达式的值,表达式最终的结果为最右端表达式的值。

1
int a=3/5,3/5+!!(3%5);//最终a的值为1

明白了这个,那么请分析下面的用法。有人依此说return是一个函数,错在哪里?

1
return (0);

当然,根本没有报错:

这里(0)实际上是(,0),一个逗号表达式,所以最终返回的结果为0,没有报错。

标准输入输出函数

首先声明一下,scanf_s,getchar_s等等带_s的函数都是微软自己定义的安全函数,仅在微软的编译器里面生效。更可恶的是,Visual Studio会强制你使用_s的函数,简直牛逼疯了。不说跨系统了,跨编译器的可移植性都有很大问题。

说句题外话,我强烈不建议使用VS这样的IDE进行C语言的学习:

  • 首先是内存太大,动辄几个G,编译器几十M就可以了啊,很容易让学习者产生编程很难的错觉;
  • 其次是想写一个程序也很麻烦,每次都要创建一个工作区。如果是小白的话,工作区是什么都不知道,只能叫怎么做就怎么做,丧失了自主性;
  • 直接使用IDE,容易让学习者忽略最基本的编译,链接这些步骤。事实证明,我现在的好多同学都对这两个概念很模糊。
  • 最后,不知道你发现没有,使用VS编译出来的可执行文件要比gcc编译出来的大很多,原因是微软会在里面添加一些奇奇怪怪的多余的东西。所以为了你程序最后的轻量高效,最好也不要使用VS。

当然,以上只是个人观点,这还是得看个人喜好。


printf()和scanf()都包括格式控制字符串和参数表。格式控制字符串又包括了格式说明符和普通字符。

格式说明符就是%c,%d,%o,%x,%f,%d之类的,还可以在中间添加修饰符。

普通字符就是一个个字符了。可以是英文字母,也可以是中文字符。从这里可见,C语言是可以输出中文的,所以一个汉字到底算不算是字符呢?先看一下下面的结果:

这里提示说“我”是“multi-character character”,即我不是一个字符。C语言里面,将汉字当作两个字符,所以下面的问题也和上面一样:

这里同样溢出了,但是打印了b;不同于上面什么都没有输出。但是问题都是一样的——一个汉字应该视为两个字符,所以也不难解释为什么要printf("%c%c%c%c%c%c\n",206,210,176,174,196,227);这样了。

scanf()的格式控制字符串要求使用者在输入数据时原封不动地将普通字符也打上去,所以,下面的代码就有点傻逼了:

1
scanf(“请输入一个数字:%d”,&a);

本意是想作为提示,结果最后用户还要把“请输入一个数字:”打上去,自己以前的做法有点搞笑。

一般scanf()的格式控制字符串都使用空格或逗号分隔:"%d %d""%d,%d",不分隔的话就要说明长度,就要用到scanf_s。


最后再回去看看上面的问题,是不是要清楚一些了?