C语言概念
C语言发展史
计算机成程序设计语言的发展阶段
- 机器语言:由0和1组成的指令序列构成程序的一种语言
- 汇编语言:使用简洁的英文字母、符号替代特定的指令序列的一种语言
- 高级语言:最接近 于数学语言或人的自然语言
2. 常量、变量、表达式
声明和定义
C语言中的声明有变量声明、函数声明、类型声明三种。从另一个角度来看,声明分为“是定义(Definition)的声明” 和 “不是定义的声明”。
那么什么样的声明同时也是定义呢?简单地说,分配存储空间的声明同时也是定义,不分配存储空间的声明不是定义。
如果一个变量声明要求编译器为其分配存储空间,那么这个声明同时也是变量的定义。
如果一个函数声明带函数体,要求编译器为它生成指令(当然,这也需要分配存储空间来保存这些指令)。那么这个声明同时也是函数的定义。
类型声明总是不分配存储空间的,所以严格来说只有类型声明而没有类型定义,但我们习惯说“定义了某种类型”。
声明也是以“;”分号结尾,这点和语句类似,但是在语法上声明和语句时有区别的,语句只能出现在函数体重,而声明即可以出现在函数体也可以出现在所有函数之外。
理解一个概念不是把定义背下来就行了,一定要理解它的外延和内涵(类似于能力边界),也就是什么情况属于这个概念,什么情况不属于这个概念,什么情况虽然属于这个概念但一般推荐的做法(Best Practice)是要尽量避免这种情况,这才算是真正理解了。
赋值
定义了变量之后,我们要把值存到它们所表示的存储空间里,可以用赋值(Assignment)语句实现。
char letter;
letter = 'a';
注意变量一定要先声明后使用,编译器必须先看到变量声明,才知道变量名(代表一块存储空间)。
另外,变量声明中的类型表明这个变量代表多大的一块存储空间,这样编译器才知道如何读写这块存储空间。
i = i + 1
还要注意的是,这里的等号不表示数学里的相等关系,和 1+1=2
的等号是不同的,这里的等号表示赋值。
在数学上,不会有i = i + 1
这种等式成立,而在C语言中这表语句表示把变量i的存储空间中的值取出来,再加上1,得到的结果在存回i的存储空间中。
在数学上,a = 7
和 7 = a
是一样的,而在C语言中后者是不合法的。
总结说来,定义一个变量,就是分配一块存储空间并给它命名。给一个变量赋值,就是把一个值保存到这块存储空间中。
变量的定义和赋值也可以一步完成,称为变量的初始化(Initialization)。其中赋值号右边的值叫做Initializer
初始化。
初始化时一种特殊的声明,而不是一种赋值语句。
int i = 1;
字符类型与字符编码
字符常量或字符型变量可当做整数参与运算
$ vim program.c
#include
int main()
{
printf("%c\n", 'a'+1);
return 0;
}
$ gcc program.c -o program
$ program.exe
b
符号在计算机内部使用数字表示,每个字符在计算机内部也使用整数表示,称为字符编码(Character Encoding)。
目前最常用的字符编码是ASCII码(American Standard Code for Information Interchange,美国标准信息交换码)。ASCII码取值范围是0~127。
char字符型本质上就是整数,只不过取值范围比int型小,我们将char和int统称为整数类型(Integer Type),简称整型。
3. 函数
数学函数
$ vim program.c
#include
#include
int main()
{
double pi = 3.14159;
double result1 = sin(pi/2);
double result2 = log(1.0)
printf("%f\n%f", result1, result2);
return 0;
}
$ gcc program.c -o program
$ program.exe
1.000000
0.000000
- 函数调用运算符是一种后缀运算符(Postfix Operator)
- 函数调用也是一种表达式
printf语句是表达式语句的一种,但printf感觉不像一个数学函数,为什么呢?因此像log这种函数,传入参数会得到返回值。至于printf,我们并不关心它的返回值,事实上它也有返回值,表示实际打印的字符数。因为调用printf不是为了得到它的返回值,而是为了利用它所产生的副作用(Side Effect)- 打印。
C语言的函数可以有副作用(Side Effect),这一点是它和数学函数在概念上的根本区别。
自定义函数
$ vim program.c
#include
// new line
void nl(void)
{
printf("\n");
}
int main(void)
{
printf("First Line");
nl();
printf("Second Line");
return 0;
}
$ gcc program.c -o program
$ program.exe
First Line
Second Line
函数原型(Prototype)
声明一个函数的名字、参数类型和个数、返回值类型,称为函数原型。可单独在函数原型后添加“;”分号结束,而且不写函数体。
void fn(void);
没有函数体的函数声明有什么用呢?它为变一起提供了有用的信息,编译器见到函数原型就明确了函数名字、参数类型和返回值。之后编译器碰到函数调用代码就知道该生成什么样的指令来实现函数调用了。所以,函数原型应该出现在函数调用之前,这也遵循“先声明后使用”的原则。
函数定义
带有函数体的声明称为函数定义,因为编译器只有见到函数体才能生成指令,分配存储空间来保存这些指令。
注意
- 如果调用函数时参数列表为空,并且缺乏函数原型,则编译器根据隐式声明规则认为参数类型是void。
- 如果声明函数时参数列表为空,则这个声明属于OldStyle语法,不算是函数原型,编译器认为参数类型和格式没有明确指出。
形参和实参
实参(parameter)和形参(argument),形参相当于函数中定义的函数,调用函数传递函数的过程,相当于定义形参并用初始化。
定义带参数的函数,需在函数定义中指明参数的个数和类型,定义参数和定义变量一样。
$ vim program.c
#include
//define function with arguments
void ptime(int hour, int minute, int second)
{
printf("%d:%d:%d", hour, minute, second);
}
int main(void)
{
ptime(0,0,0);
return 0;
}
$ gcc program.c -o program
0:0:0
非定义的函数声明(函数原型)则只写参数类型,而不写参数名。
全局变量、局部变量、作用域
将函数中定义的变量称为局部变量(Local Variable),由于形参相当于函数中定义的变量,所以形参也是一种局部变量。这里局部包含两层含义:
- 一个函数中定义的变量不能被另一个函数使用
- 每次调用函数时局部变量都表示不同的存储空间,局部变量在每次函数调用时分配存储空间,在每次函数返回时释放存储空间。
与局部变量相对的是全局函变量(Global Variable),全局变量定义在所有的函数体之外,他们在程序开始运行时分配存储空间,在程序结束时释放空间,在任何函数中都可以访问全局变量。
$ vim program.c
#include
int hour=0,minute=0;
void ptime(void)
{
printf("%d:%d", hour, minute);
}
int main(void)
{
ptime();
return 0;
}
$ gcc program.c -o program
$ program.exe
0:0
虽然全局变量用起来很方便,但一定要慎用,能用函数传参代替的就不要用全局变量。
若全局变量和局部变量重名后会怎么样呢?
局部变量可用类型相符的任意表达式来初始化,而全局变量只能用常量表达式(Constant Exression)来初始化。为什么要这样规定呢?
因为程序运行一开始,在还没开始执行main函数中的任何语句之前,就要用初始化来初始化全局变量。这样,main函数的第一条语句就可以获取全局变量的初始化值来初始化全局变量。这样,main函数的第一条语句就可以获取全局变量的初始值来做计算。要做到这一点,初始值必须保存在编译生成的可执行文件中,因此要求初始值必须在编译时就计算出来。
由于编译器负责计算全局变量的初始值,为了简化编译器的实现,C语言从语法上规定全局变量只能常量表达式来初始化。
如果全局变量在定义时不初始化则初始值为0,如果局部变量在定义时不初始化则初始值是不确定的。所以,局部变量在使用前一定要先赋值,如果基于一个不确定的值做后续计算肯定会引入bug。
#include
#define HEIGHT 10
int calculate(int l, int w);
int main()
{
int inputLong;
int inputWidth;
int result;
printf("default height is \n%d\n", HEIGHT);
printf("please input long\n");
scanf("%d", &inputLong);
printf("please input width\n");
scanf("%d", &inputWidth);
result = calculate(inputLong,inputWidth);
printf("calculate result is\n%d", result);
return 0;
}
int calculate(int l, int w)
{
int result = l*w*HEIGHT;
return result;
}
page44
格式化输入输出
在探索难以实现的问题时,简化是唯一的方法。
C语言使用scanf
和printf
函数来支持格式化读写。
printf函数
printf函数被设计用来显示格式串(format string)的内容,并在字符串指定位置插入可能的值。
printf(格式串, 表达式1, 表达式2, ...)
调用printf时必须提供格式串和用于打印时插入到字符串中的任意值。
格式串包含普通字符和转换说明(conversion specification),转换说明以字符%
开头,用来标识打印过程中填充了值的占位符。跟随在%
后的是指定将数值从内部(二进制)形式转换成打印(字符)形式的方法。
- %d 将int类型数值从二进制转换为十进制数字组成的字符串
- %f 对float类型数值转换
$ vim program.c
#include
int main()
{
int i;
float f;
i = 10;
f = 3.14159;
printf("i=%d\nf=%f\n", i, f);
return 0;
}
$ gcc program.c -o program
$ program.exe
i=10
f=3.141590
C语言编译器不会检测格式串中转换说明的数量是否和输出项的数量项匹配。
$ vim program.c
#include
int main()
{
int i;
float f;
i=10;
f=3.14159;
printf(" i=%d\n f=%f\n x=%d\n y=%f\n", i, f);
return 0;
}
$ gcc program.c -o program
$ program.exe
i=10
f=3.141590
x=4200752
y=50.123535
C语言编译器也不会检测转换说明是否适合要显示项的数据类型。
$ vim program.c
#include
int main()
{
int i;
float f;
i = 10;
f = 3.14159;
printf(" i=%d\n f=%d\n", f, i);
return 0;
}
$ gcc program.c -o program
$ program.exe
i=0
f=0.000000
转换说明