2010-03-31 15:10:47 阅读5 评论0 字号:大中小
我们稍微的解释了一下上面给出的术语。上面的表格给出了C的一个概貌。现在让我们来看一看一些细节。
标识符实际上就是一个名字。它要么是一个行为的,是一段数据的或者是一个几种可能状态列觉的名字,等等。C语言使用下面的规则来考虑名字:
1)字母A-Z,a-z和下划线“_”(英文输入法中与减号在同一个位置上)。
2)然后是数字,但是标识符的开头不能是数字。
3)小写和大写被认为是不同的。
4)Lcc-win的标识符的{zd0}长度为255个字符。标准为保证31个字符有意义的外部为一个外部标识符,63为为内部的。如果你使用了超过的长名字,你的代码可能不能再其他环境里工作。
标识符是你的软件的词汇表。当你创建它们的时候,这样就给出了助记符,这个助记符告诉你数据在那个地方存放。
下面是一些你可能想要遵守的规则:
l 大多时候,从完整的单词,使用下划线连接或隐式的使用大写连接来构造标识符。例如list_element 或ListElement.这个规则的一个变形时“驼峰规则”:{dy}个单词是小写,第二个的开头用大写。这样就是listElement.
l 对于那些在一个包中频繁使用的单词,谨慎的时候缩写。尽可能的保持一致。
l 标识符的名字应该随着它们的作用范围的增长而增长:一个本地函数的标识符可以比那些在整个包中都使用的名字要短一点。
l 包含双下划线(“__”)或已一个下划线和一个大写字母开头的标识符被编译器保留,因此程序员不应该使用。从安全的角度来说,{zh0}避免使用所有已下划线开头的标识符。
微软有一个发展的大的规则集合,最有意义的一个是:
表达式可以出现在常量的定义里,以和这个程序在执行时一样的方法来执行。例如下面将会把1放到常量d 中:
static int d = 1;
下面的也会把一放到变量d中:
static int d = 60 || 1+1/0;
为什么?
表单时60|| 1+1/0从左向右计算。它是一个布尔表单时,如果{dy}个表达式不等于零的话它的值就是1,如果{dy}的值是0的话,它的值就是第二个表达式的值。因为60不是零,我们不用计算第二个表达式而马上停止了,幸运的是第二个表达式中有一个错误……
整数常数以数字开头,但是不包括小数部分和指数部分。它可能有一个指定他的基的前缀和一只指定它的类型的后缀。一个十进制的常数以一个非零的数字开头并有一个十进制数字序列组成。一个八进制的常数以0为前缀然后可以跟上上有0到7数字组成的序列。一个十六进制的常量有前缀0x或0X后跟十进制数字和字母a(或A)到f(或F)代表从10到15的值组成的序列。下面是一些不同的整数常量的例子。
12345 (整数常量,十进制)
0777 (十进制511的八进制值)
0xF98A (十进制63882的十六进制。返回一个无符号的类型)
12345L (长整数常量)
2634455LL (long long 证书常量)
2634455i64 (long long 证书常量, 微软的标记法)
5488UL (无符号长整数常量5488)
548ULL (无符号long long 整数常量548)
对于浮点常量,惯例是要门使用十进制点(1230.0)或科学计数法(在这个形式里为1.23e3).它们可以有后缀“F”(或“f”)来标明它们是浮点常数,而不是当没有后缀的时候的隐式假设中的双进度常量。一个后缀“l”或“L”意味着长双进度常量。一个后缀“q”或“Q”意味着qfloat。默认的格式(没有任何的后缀)是双精度。这是很重要的因为这可能是很难找到的错误的原因所在。例如:
Long double d = le800;
一个long double的动态范围是最够大来接受这个值的,但是因为程序员忘记了L这个数字将会对读作双精度。因为双精度的数不能保存这个值,结果就是初始化xx不工作。
字符串常量被围在双号里面。如果在双引号之前直接加一个“L”意味着这是一个字节的字符串。例如:
L"abc"
这意味着编译器将会被这个字符串转换为一个宽字符的串并以双字节的字符串来代替ASCII字符的串。
在字符串中包含算引号的时候,必须使用一个反斜杠来处理。例如:
"The string \''the string\'' is enclosed in quotes"
注意字符串和数值时xx不同的数据类型。即使一个字符串只包含数字,它也绝不会被编译器认作是数字的:“162”是一个字符串,要把它转换为一个数值你必须明确的写代码来完成这个转换。
在字符串中下面的缩写词可以被认出:
缩写 | 意义 | 值 |
\n | 新行 | 10 |
\r | 回车 | 12 |
\b | 退格 | 8 |
\v | 垂直制表 | 11 |
\t | 制表 | 9 |
\f | Form feed | 12 |
\e | Escape(逃脱) | 27 |
\a | 响铃 | 7 |
\\ | 反斜杠 | \ |
\'' | 添加一个双引号 | " |
\x<十六进制数> | 在当前的位置插入一个十六进制数字表示的字符。 | 因为可以录入任何的数字,所以值可以是任何东西。例如“ABC\A”等价于“ABC\n” |
\<八进制数> | 和\x的情况类似,但是使用三个八进制数字,也就是以八位基的数。注意不需要在反斜杠后面添加任何的特定字符。八进制数字直接在其后。 | 任何值。例如字符串"ABC\012"等价于"ABC\n" |
字符串常量太长不能在一行写下的话可以有两种方法来处理:
char *a = "This is a long string that at the end has a backslash \
that allows it to go on in the next line";
因为一种方式,在C99中被引入:
char *a = "This is a long string written"
"in two lines";
还要注意字符串常量不能被程序修改。Lcc-win储存所有的字符串常量一次,即使他们才程序中出现了多次。例如如果你写:
char *a="abc";
char *b="abc";
A和b是相同的串,如果其中一个修改了,另一个也不会保持原始的值。
这里有几个实用数组的例子:
int a[45]; // Array of 45 elements
a[0] = 23; // Sets first element to 23;
a[a[0]] = 56; // Sets the 24th element to 56
a[23] += 56; // Adds 56 to the 24th element
char letters[] = {‘C’, ‘-’, ‘-’};
注意{zh1}一个数组“letters”不是一个零结尾的字符串而是一个有3个位置的数组,这个数组不是以0字节结尾的。
多维数组如下来标记下表:
int tab[2][3];
...
tab[1][2] = 7;
一个行三列的表被声明。然后我们把7赋给了第二行的第三列。(记住:数组的索引以0开始)。
注意当你只实用一个下标来索引一个两位数组的时候,你得到的是一个指向这个指定行的开始位置的指针。
int *p = tab[1];
现在p包含了第二行的开始的位置的地址。
数组在C中式以行优先的顺序来存储的,也就是说数组是一个紧邻的内存片段而数组的行被一个接着一个的存储。每一个数组元素实用简单的方式来访问:
x[i][j] == *(x+i*n+j)
这里n是数组x的行的大小。编译器对待两维数组和一位数字形式的不同是很明显的,因为两维数组需要多个信息片段来访问两维中的一个:每一行大大小,在这个例子里就是“n”。
编译器怎样知道这个值呢?从数组声明的过程中得到。当编译器解析到一个如下的申明的时候
int tab[5][6];
{zh1}一个数字(6)是每一行的大小,也是编译器在编译每一个数组访问时需要知道的{wy}的信息。因为数组时以指针传递给函数的,当你传递一个两位数组的时候也需要传递它的这个信息,例如通过可把表示列的数字留着不填,就像:
int tab[][6]
这是传递二维数组给函数的标准方法。例如
#include <stdio.h>
int tab[2][3] = {1,2,3,4,5,6};
int fn(int array[][3]) // Note the declaration of the array parameter
{
printf("%d\n",array[1][1]);
}
int main(void)
{
fn(tab);
}
数组可以被修改,也就是说他们的维数在编译时决定或者他们是动态的也就是说他们的维数在运行时决定。对于动态的数组,我们不得不为分配我们需要的存储空间做两部处理,而和一维数组相比我们只需要一次分配。
例如,下面就是我们动态的为3行4列的整数数组分配而写的代码:
int ** result = malloc(3*sizeof(int *));
for (int i = 0; i<3;i++) {
result[i] = malloc(4*sizeof(int));
}
当然在真的程序中我们总是要测试result的malloc的值是不是失败。
我们看到我们首先分配一个数组指针,这个数组指针等于我们需要的行数。然后我们使用分配到的这个数组列数的大小倍的我们要在这个数组中存储的对象——在这个例子里就是一个整数——的大小的空间来填充每一行。
区分动态分配和编译时固定的数值时非常重要的。对于动态数组来说行优先的模式不存在,行优先只对在编译时已经知道维数的数组有效。
从上面的讨论中我们知道我们总是需要一个和这个数组的行数一样多指针的数组,有时候在对于位数已知的数值我们不需要这样做。
很明显,如果你想在这两个选项中选出{zh0}的你可以为这个而为数组分配一个单独的内存块,并用指针标记来替换正在使用的数组标记,来访问数组,排除为增加存储的需要。你可以像这样来分配两维数组:
int *result = malloc(sizeof(int) * NbOfRows * NbOfColumns);
你可以这样来访问数组:
result[row*NbOfColumns+column];
注意你必须保数组维的分离。
指针数组可能有点笨拙,但是他给了我们更多的自由。我们可以很容易的把一个新行添加到数组中,例如在{dy}行和第二行之间。我们只需要在{dy}个指针后面添加一个指针,让后把其他的指针依次后移就行了。我们一点也不用移动数据。如果不按我们上面描述的捷径来做。我们就必须为额外的行而移动数据。在编译时定义的数组情形下,做任何事情都是不可能的因为数组的维数被编译器编译在每一个访问中了。
变长数组是基于在程序运行时计算而不是当程序被编译时计算表达式的数组。下面是这个结构的一个例子:
int Function(int n)
{
int table[n];
}
叫做“table”的整数数组有n个元素。这个“n”是作为实参传递给函数的,所以它的值不能提前知道。编译器生成进入这个函数是要运行的为这个数组分配空间的代码。被数组使用的存储空间在这个函数退出的时候会制动的释放。
你可以使用类似于一个转型表达式的结构来标记一个复合类型的纸面值。例如:
typedef struct tagPerson {
char Name[75];
int age;
} Person;
void process(Person *);
...
process(&(Person){“Mary Smith” , 38});
这是C99的一个新特新之一。纸面值应该被大括号括起来,而且应该和期望的结构相匹配。下面的例子这仅仅是一个“语法的糖块”(syntactic sugar):
Person __998815544ss = { “Mary Smith”, 38};
process(&__998815544ss);
优点是现在指出这个结构的名字不再是必须的因为编译器为你做了这些工作。然而从内部来说,这个代码xx地表示出了在lcc-win中发生了什么。
sqrt( hypo(6.0,9.0) ); // Calls the function hypo with
// two arguments and then calls
// the function sqrt with the
// result of hypo
参数可以使任何类型对象的表达式。在位调用一个函数做准备的时候,实参被计算出来,而每一个形参被相应的实参赋值。
一个行数可能改变它的形参的值,但是这些改变它的实参的值。另一方面,传递一个对象的指针是可能的,而且函数可能改变这个指针指向的对象的值。
一个被声明为数组或函数类型的形参被转化为一个指针类型的形参。
计算实际的实参的顺序和在实际的实参中的字表达式没有指定。例如:
fn( g(), h(), m());
这里行数g(),h()和m()的调用顺序没有指定。
对于函数指针的语法和对于一般的函数调用的语法类似。没有必要再次应用到这个函数的一个指针。例如:
int main(void)
{
int (*fn)(int);
(*fn)(7);
fn(7);
}
两次调用都可以工作。
如果被调用的行数有原型,当可能的时候实参将会隐式的转换为形参对应的类型。当这个转换失败之后,lcc-win产生一个错误,让后编译失败。其他的编译器可能有不同的处理。
当一个函数没有原型的时候,或者当一个函数有一个可变长度的实参列表的时候,对于每一个实参将会应用默认的实参提升。整型提升被应用在每一个参数上,而float形参被作为double传递。
要使用额外的参数你需要包含<stdarg.h>。要访问添加的实参,你需要执行va_start,让后对每一个参数,你需要执行一个va_arg。注意,如果你已经执行了宏va_start,在这个行数退出之前,你总是应该执va_end宏。下面是一个例子把传递给他的任意多的整数加在一起。传递进来的{dy}个整数是后面的整数的个数。
#include <stdarg.h>
int va_add(int numberOfArgs, ...)
{
va_list ap;
int n = numberOfArgs;
int sum = 0;
va_start(ap,numberOfArgs);
while (n--) {
sum += va_arg(ap,int);
}
va_end(ap);
return sum;
}
我们可以这样来调用这个函数
va_add(4s,987,876,567,9556);
或者
va_add(2,456,789);
在32位的系统(Linux或Windows)下,个数可变的实参的区域实际上仅仅是一个栈区域的顶点。当你做一个va_start(ap)的时候,系统要把ap指针指向实参的开通,让后仅仅放回地址。
之后,当你从可变的实参列表中得到一些东西的时候,这个指针被传递进来的参数的大小所增加,并取为整数指向先一个。这非常的简单而且在大多数的系统上工作。
其他的系统,特别是Windows64位或Linux64为需要更复杂的模式因为参数不是传递到栈里而是在预先定义的寄存器里。这就强迫编译器在栈区域中节省所有可能的寄存器,并从寄存器中读取实参。问题出现在和未来的兼容上,因为一些参数被传递到一些寄存器集合里(例如整数实参和浮点数实参被传递的寄存器是不同的),而且编译器编译器需要保持指向不同栈区域的指针。