const
修饰的变量为"只读"变量,它有就近原则:和谁挨的近就修饰谁。如:
const int** a = malloc(sizeof(int*));
int b = 3;
*a = &b;//允许
int* c;
a = &c;//允许
//**a = 4; //不允许,因为**a是只读的。
const与int离的近,所以最终的int数值无法被修改,而a与*a可以被修改。
在第一句中,如果不加malloc(),会报错。因为声明变量时,只分配了一个用于存储a的值的空间(假设a的值为0xee),而0xee所指向的内存并没有被分配给该程序,所以*a中无法存储&b的值。但即使不加malloc(),a=&c一样是可以的,因为&c存储于a中。
1,修饰普通变量,用于声明一个常量,类似于#define。如const double PI = 3.14;。
2,指向常量的指针,则不可以使用指针修改它所指向数据的值,但允许修改指针的数据。如:
int a[] = {3,4,5};
const int* pa = a;
a[0] = 5;//允许
*pa = 4;//不允许
第二行代码把指针声明为指向const类型(本例中为const int)的指针,此时不能使用pa对它指向的整形变量进行修改,即不能直接对*pa进行赋值。但a并不是const的,所以a[0]=5是允许的。
第二行中,并没有将pa声明为常量,只是将*pa声明为常量(只对pa来说是常量,通过pa无法修改,但通过a就可以进行修改)。
3,可以将指针声明为常量,如int * const a;则a是一个常量,它指向一个int变量的地址。此时可以修改*a的值,但无法修改a的数值。即允许*a = 8;但不允许a++。
4,修饰数组时,整个数组中的所有元素都无法进行修改。因为数组名是一个指针,使用const修饰后,该指针就指向常量,无法通过指针进行修改。
5,可以将常量或非常量数据的地址赋值给指向常量的指针,但不能将常量数据的地址赋值给普通的指针。因为通过普通指针可以修改非常量数据。
const int b =3;
int* pa = &b;//不允许
*pa = 4;//如果上句允许,那么就可以通过*P修改b的值,达不到b是const常量的目的
6,把非const变量的地址赋值给指向const
volatile
该关键字提醒编译器,它后面的变量是易变的,每一次读取该值时都需要从它的地址中读取,而不是使用缓存。
未使用该关键字时,编译器有可能将一个连续使用的非改变值的变量存储到寄存器中,这个过程称为缓存。被缓存的变量可能由其他程序改变了其值,将导致不同程序取值不一样(一个程序使用寄存器中的,一个使用本身的)。如下
val1 = x;
//未使用x的代码
val2 = x;
上述代码中两次使用了变量x而未改变它的值,某些编译器会将变量存储到寄存器中,当再次读取x时,就直接从寄存器而非原始内存中读取以节省时间,这个过程即是缓存。
通常缓存是一个很好的处理方式,但如果这个过程中别的程序个性了x的值,就不那么美好了。因为x已经缓存到寄存器中,因此val2取的仍旧是x未被修改之前的值。这就导致了不同程序取得的x值不一致。
而volatile却是告诉编译器,不使用缓存,因此避免了此各情况。
restrict
它只用于指针,告诉编译器该指针是访问一个数据对象的唯一且初始的方式。
多文件共享数据
在开发中,通常会有一些数据是所有文件共享的。实现共享有两种方式:
一,在一个文件中进行声明定义,在别的文件中进行引用声明(使用extern关键字)。如
//file1.c——定义全局变量
const double PI = 3.14;
//file2.c——引用全局变量
extern const double PI;
二,将一些常量放在头文件中,使用的时候通过include ""引入相应的头文件。如:
//file1.h——定义常量
static const double PI = 3.14;
//file2.c——引用头文件
#include "file1.h"
在该种方式中,必须将变量声明为static类型的全局变量。因为不同文件include进同一个头文件后,就会导致不同的文件拥有同一标识符的定义声明,ANSI不允许这么做。
将变量声明为static后,每一个文件都拥有一个独立的数据复本,并且每一个全局变量只拥有内部链接,不会引起冲突。
比较
第一种方式:需要在一个文件中为每一个变量进行定义声明,在另一个文件中进行引用声明,所以文件使用的是同一个变量,而不是一个变量的不同复本。
第二种方式:定义在头文件中,不需要在每一个文件中进行引用声明,但每一个文件都是使用的变量复本,如果头文件中包含巨大的数组,就容易引进内存问题。
inttypes.h与stdint.h
由于int,long等类型的位数随操作系统的不同而不同,为解决该问题,c99提供了一个可选名字的集合,以确切地描述有关信息。这些信息定义在inttypes.h中的,所以使用时必须引入inttypes.h文件(如果不行,就引入stdint.h文件)。int16_t:存储空间肯定为16位的整数类型。比如在某些系统中short为16位,那么该值表示short;在另一些系统中int为16位,该值代表着int。使用该种类型时会存在一个潜在问题:某个系统可能不支持一些选择。如不能保证某个系统上存储int8_t(8位有符号整数)类型。因此,又定义了int_least*_t这种类型。
int_least8_t:存储空间至少有8位,且最接近8的整数类型。比如某系统中没有8位的数据类型,short为16位,int为32位,那么该值代表short。因为short的16最接近于8。
int_fast16_t:存储空间至少为16位,且计算速度最快的数据类型。例如int_fast8_t表示:对8位有符号数而言计算速度最快的类型的别名。
intmax_t:当前系统中位数最多的整数数据类型。
其余的与此类似,前面加u表示无符号,如uint32_t表示一个无符号的32位的整数类型。
上面定义的几种类型都可以看作是在特定操作系统中对某个类型(short,int,long,long)的别称。
main函数
main函数可以没有参数或者有两个参数。有两个参数时,第一个表示命令行中的字符串数,为int类型,一般记为argc(argument count);第二个参数是一个指向字符串的指针数组(char*[]),命令行中每一个字符串被存储到内存中,并分配一个指针指向它,最终这些指针都会被保存在该指针数组中。该参数一般记为argv(argument values),在大多数操作系统中会被程序的名字赋值给argv[0]。
main函数一般由系统进行调用,因此main中的参数来源于操作系统——命令行中输入的,此即为命令行参数。
声明
在进行声明时,可以添加一些修饰符修改变量名。常用的有:*,[]与()。前者表示指针,[]表示数组,()表示函数。它们具有如下规则:
1,[]与()具有相同的优先级,并且都高于*。因此int * a[10];表明a是一个数组,而数组中的元素才是int*类型的。
2,[]与()都是从左到右进行结合的。int good[12][50]表示good是一个拥有12个元素的数组。
3,可以使用小括号()提升优先级。如int (*a)[10]首先意味着a是一个指针。
对于int *a[3][4]:首先说明a是一个具有三个元素的数组,a与[3]结合完毕之后再也[4]结合,又表示a中每一个元素都是具有4个元素的数组,即a是一个3*4的二维数组。最后再也int*结合,又表明这个二维数组中的每一个元素都是一个int*类型的。如果写成int (*a)[3][4],那么a就是一个指针,指向3*4二维数组的指针。
再比如char (* f[3])():首先()中*f[3]表明f是一个数组,而*又表明f中每一个元素都是一个指针,小括号外面的又表明该指针指向的类型为char (),即返回类型为char的函数。