Linux C 学习笔记

一个好的习惯是打开gcc的-Wall选项,也就是让gcc提示所有的警告信息

可以把整个程序写成一行,但是include必须单独占一行

转换说明和转义序列又有区别:转义序列是编译时处理的,而转换说明是在运行时调用printf函数处理的。源文件中的字符串字面值是"character: %c\ninteger: %d\nfloating point: %f\n",\n占两个字符,而编译之后保存在可执行文件中的字符串是character: %c换行integer: %d换行floating point: %f换行,\n已经被替换成一个换行符,而%c不变,然后在运行时这个字符串被传给printf,printf再把其中的%c、%d、%f解释成转换说明。

C语言中的声明(Declaration)有变量声明、函数声明和类型声明三种。如果一个变量或函数的声明要求编译器为它分配存储空间,那么也可以称为定义(Definition),因此定义是声明的一种。

初始化是一种特殊的声明,而不是一种赋值语句。赋值语句就是一种表达式语句。

使用math.h中声明的库函数还有一点特殊之处,gcc命令行必须加-lm选项,因为数学函数位于libm.so库文件中(这些库文件通常位于/lib目录下),-lm选项告诉编译器,我们程序中用到的数学函数要到这个库文件里找。本书用到的大部分库函数(例如printf)位于libc.so库文件中,使用libc.so中的库函数在编译时不需要加-lc选项,当然加了也不算错,因为这个选项是gcc的默认选项。

在Linux平台上最广泛使用的C函数库是glibc,其中包括C标准库的实现,也包括本书第三部分介绍的所有系统函数。几乎所有C程序都要调用glibc的库函数,所以glibc是Linux平台C程序运行的基础。glibc提供一组头文件和一组库文件,最基本、最常用的C标准库函数和系统函数在libc.so库文件中,几乎所有C程序的运行都依赖于libc.so,有些做数学计算的C程序依赖于libm.so,以后我们还会看到多线程的C程序依赖于libpthread.so。以后我说libc时专指libc.so这个库文件,而说glibc时指的是glibc提供的所有库文件。

$?是Shell中的一个特殊变量,表示上一条命令的退出状态。

操作系统在调用main函数时是传参数的,main函数最标准的形式应该是int main(int argc, char *argv[])。C标准也允许int main(void)这种写法,如果不使用系统传进来的两个参数也可以写成这种形式。

现在澄清一下函数声明、函数定义、函数原型(Prototype)这几个概念。比如void threeline(void)这一行,声明了一个函数的名字、参数类型和个数、返回值类型,这称为函数原型。在代码中可以单独写一个函数原型,后面加;号结束,而不写函数体,例如: void threeline(void); 这种写法只能叫函数声明而不能叫函数定义,只有带函数体的声明才叫定义。上一章讲过,只有分配存储空间的变量声明才叫变量定义,其实函数也是一样,编译器只有见到函数定义才会生成指令,而指令在程序运行时当然也要占存储空间。那么没有函数体的函数声明有什么用呢?它为编译器提供了有用的信息,编译器在翻译代码的过程中,只有见到函数原型(不管带不带函数体)之后才知道这个函数的名字、参数类型和返回值,这样碰到函数调用时才知道怎么生成相应的指令,所以函数原型必须出现在函数调用之前,这也是遵循“先声明后使用”的原则。

函数的隐式声明(Implicit Declaration),在main函数中调用threeline时并没有声明它,编译器认为此处隐式声明了int threeline(void);,隐式声明的函数返回值类型都是int,由于我们调用这个函数时没有传任何参数,所以编译器认为这个隐式声明的参数类型是void,这样函数的参数和返回值类型都确定下来了,编译器根据这些信息为函数调用生成相应的指令。然后编译器接着往下看,看到threeline函数的原型是void threeline(void),和先前的隐式声明的返回值类型不符,所以报警告。

扫描二维码关注公众号,回复: 7030886 查看本文章

每次调用函数时局部变量都表示不同的存储空间。局部变量在每次函数调用时分配存储空间,在每次函数返回时释放存储空间,例如调用print_time(23, 59)时分配hour和minute两个变量的存储空间,在里面分别存上23和59,函数返回时释放它们的存储空间,下次再调用print_time(12, 20)时又分配hour和minute的存储空间,在里面分别存上12和20。 与局部变量的概念相对的是全局变量(Global Variable),全局变量定义在所有的函数体之外,它们在程序开始运行时分配存储空间,在程序结束时释放存储空间,在任何函数中都可以访问全局变量

局部变量可以用类型相符的任意表达式来初始化,而全局变量只能用常量表达式(Constant Expression)初始化。全局变量在定义时不初始化则初始值是0,如果局部变量在定义时不初始化则初始值是不确定的。所以,局部变量在使用之前一定要先赋值,如果基于一个不确定的值做后续计算肯定会引入Bug。

写非定义的函数声明时参数可以只写类型而不起名,例如上面代码中的void print_time(int, int);,只要告诉编译器参数类型是什么,编译器就能为print_time(23, 59)函数调用生成正确的指令。另外注意,虽然在一个函数体中可以声明另一个函数,但不能定义另一个函数,C语言不允许嵌套定义函数[5]。

结构体变量也可以在定义时初始化,例如:

struct complex_struct z = { 3.0, 4.0 }; Initializer中的数据依次赋给结构体的各成员。如果Initializer中的数据比结构体的成员多,编译器会报错,但如果只是末尾多个逗号则不算错。如果Initializer中的数据比结构体的成员少,未指定的成员将用0来初始化,就像未初始化的全局变量一样。例如以下几种形式的初始化都是合法的: double x = 3.0; struct complex_struct z1 = { x, 4.0, }; /* z1.x=3.0, z1.y=4.0 */ struct complex_struct z2 = { 3.0, }; /* z2.x=3.0, z2.y=0.0 */ struct complex_struct z3 = { 0 }; /* z3.x=0.0, z3.y=0.0 */ 注意,z1必须是局部变量才能用另一个变量x的值来初始化它的成员,如果是全局变量就只能用常量表达式来初始化。这也是C99的新特性,C89只允许在{}中使用常量表达式来初始化,无论是初始化全局变量还是局部变量。 {}这种语法不能用于结构体的赋值,例如这样是错误的: struct complex_struct z1; z1 = { 3.0, 4.0 }; 以前我们初始化基本类型的变量所使用的Initializer都是表达式,表达式当然也可以用来赋值,但现在这种由{}括起来的Initializer并不是表达式,所以不能用来赋值[14]。

有些时候结构体或数组中只有某一个或某几个成员需要初始化,其它成员都用0初始化即可,用Designated Initializer语法可以针对每个成员做初始化(Memberwise Initialization),很方便。例如: struct complex_struct z1 = { .y = 4.0 }; /* z1.x=0.0, z1.y=4.0 */

猜你喜欢

转载自www.cnblogs.com/gzwangjiaxiang/p/11357598.html