@SiberiaBear
2015-10-14T21:10:58.000000Z
字数 5995
阅读 1656
C语言
学习前辈的一个bug,在注释一行后的末尾不能加‘\’,‘\’的意思是该行没结束,则注释会把下一行也注释掉,但是软件不会显示下一行被注释了。例如:
main()
{
// this is a comment and with a endding of \
other_codding();
}
这个程序的注释(第一行)将第二行的函数也一起注释了,但是软件没有用颜色标示下边也被注释。
单片机软件编译过程中有这么个问题,我用的是CCS,当你写如下代码:
main()
{
int a=0;
while(1)
{
a = a + 1;
}
}
软件在编译时会将“a = a + 1;”这句话直接忽视掉,不编译。原因是a的变化并未对单片机产生任何影响,只对本身产生影响,所以不编译,被优化掉。
在程序for循环中,递减循环要比递增循环更好一些,适应递减,更容易被操作系统或单片机监测。
main()
{
int a;
for(a=100;a>0;a--); //better
}
C语言在同一运算符中的多个操作数之间未规定计算顺序(&&、||、?:和,运算符除外),所以可能会出现错误。
printf("%d %d\n",++n, power(2,n));
在这个运算中,可能先计算++n,再调用power(),也可能先调用power(),在计算++n。所以会出现错误。再如:
x = acc() + mic();
在这个运算中,可能先调用acc(),再调用mic(),或者相反,如果两个函数之间存在互相的全局参数调用修改,可能会出现错误。
将外部变量的声明与定义严格区分开来很重要。变量声明用于说明变量的属性(主要是变量的类型),而变量定义除此之外还将引起存储器的分配。
int sp;
double val [MAXVAL];
extern int sp;
extern double val []
前边两条语句是变量定义,会为之分配存储单元,同时这两条语句为该源文件中其余部分的声明。
后边两条语句声明外部变量,val 数组的长度在其他地方确定。
static关键字可以用于声明外部变量、函数或内部变量。当static用于声明外部变量时,这样的变量将不能被同一程序的其他文件所访问,而只能被被声明的该文件内的函数所访问,起到隐藏外部变量的功能。例如:当file.c文件中声明了两个外部变量与函数,若想只允许该函数使用这两个外部变量而不允许main.c文件使用,则可以将这两个外部变量声明为静态变量,这样这两个外部变量将只对于该文件内有效。
同理,static也可以用于声明函数。通常来说,函数是可以全局访问的,如果把函数声明为static类型,则该函数只在该函数声明所在的文件内可见,在其他文件内均无法访问。
当static用于声明内部变量时,则该变量只能在该函数中有效,但它与自动变量不同的是,不管函数是否存在,它将始终占用内存,不会随着函数被调用与退出而存在或消失。
static修饰内部静态变量时的作用域也在函数内部,虽然函数调用结束不会被销毁,但静态变量在函数之外不可见。
自动变量,是指在定义它们时才创建,在定义它们的函数返回时系统将回收该变量所占用的内存空间。一般,不做专门说明的局部变量都是自动变量,用auto关键字说明,可省略。自动变量只有一种存储方式,就是存储在栈中,所以它的作用域只在函数内。
宏定义的两个不清楚的地方:①宏定义中的形式参数不能用带引号的字符串替换。如:
#define f(b) printf("b" "%d",b)
b参数将只替换printf函数中第二个b;如果必须要替换,则在替换的文本行中,参数以#作为前缀,例如:
#define f(b) printf(#b "%d", b)
这样,当使用语句f(mm)
调用该宏时,将被扩展为:
printf("mm" "%d", mm)
②预处理运算符##
为宏扩展提供了一种连接实际参数的手段。如果替换文本中的参数与##
相邻,则该参数将被实际参数替换,##
与前后的空白符将被删除,并对替换后的结果重新扫描。如,使用paste
连接两个参数:
#define paste(front, back) front ## back
当使用past(name, 1)
宏调用时,结果将是name1
指针和数组的一个区别。如下两句定义
char amessage[] = "now is the time"; //定义一个数组
char *pmassage = "now is the time"; //定义一个指针
上述声明中,amessage是一个仅仅足以存放初始化字符串以及空字符'\0'的一维数组。数组中的单个字符可以进行修改,但amessage始终指向同一个存储位置。另一方面,pmessage是一个指针,其初值指向一个字符串常量,之后它可以被修改以指向其他地址,但如果试图修改字符串的内容,结果是没有定义的。
如果将二维数组作为参数传递给函数,那么在函数的参数声明中必须指明数组的列数。数组的行数没有太大关系,因为前面已经讲过,函数调用时传递的是一个指针,它指向由行向量构成的一维数组。如,假设tab[2][13]是一个二维数组,将它作为参数传递给函数func(),那么func()可以写成:
func(int tab[2][13]){...}
或者
func(int tab[][13]){...}
或者
func(int (*tab)[13]){...}
最后一种声明形式表示参数是一个指针,它指向具有13个整型元素的一维数组。因为方括号的优先级高于*的优先级,所以上述声明中必须使用圆括号。如果去掉括号,变成:
func(int *tab[13]){...}
这相当于是声明了一个数组,该数组有13个元素,其中每个元素都是一个指向整型对象的指针。
一般说,多维数组中除数组的第一维(下标)可以不指定大小外,其余各维都必须明确大小。
指向函数的指针的声明为:
int (*func)(void);
而下句声明:
int *func(void);
则表示func是一个函数,该函数返回int类型的指针。
条件编译语句#if
中不能使用sizeof
,因为预处理器不对类型名进行分析。但预处理器并不计算#define
语句中的表达式,因此,在#define
中使用sizeof
是合法的。
两个指针的加法是非法的,而指针之间的减法运算却是合法的,如:定义两个指针分别指向一个数组的始末:
char *low, *high;
mid = (low+high) / 2; //这样计算中间指针是行不通的
mid = low + (high - low) / 2; //但这样算是可以的
typedef
与#define
的区别:typedef
只是为了增加可读性而为标识符另起的新名字,而#define
原本在C中是为了定义常量的,到了C++,也逐渐成了起别名的工具。有时很容易搞不清楚两者的区别。比如:
typedef int INT
#define INT int
这两条语句的功能是一样的。然而,第一条语句更规范,因为在早期的许多C编译器中第二条语句是非法的,只是在现今编译器中进行了扩充。为了兼容,一般都遵循#define
定义“可读”的常量以及一些宏语句的任务,而typedef
则常用来定义关键字、比较长的类型的别名。比如:
typedef (int*) pINT;
#define pINT2 int*;
这两条语句的差别很大,实践中:定义pINT a,b;
,意思等同于int *a, *b;
,而定义pINT2 a,b;
,意思则是int*a,b;
,是不同的。
文本流由一系列行组成,每一行的结尾是一个换行符。如果系统没有遵循这种模式,则标准库将通过一些措施使得该系统适应这种模式。例如,标准库可以在输入端将回车符和换行符都转换为换行符,而在输出端进行反向转换。
在头文件<stdio.h>
中,如getchar()
、putchar()
等“函数”,其实都是宏定义,这样可以防止在处理每个字符时都要调用一遍函数,缩减了程序开销。
对于标准输出函数printf()
中格式转换符%
的一些说明:
%
后边接有如下几种符号(按顺序依次,除s
外均为可选):
- -
,代表将要输出的字段左对齐,如果没有该负号符,则默认右对齐;
- 数字
或*
,代表最小输出的位数,如果将要输出的字段的长度大于该数字,则输出全部的长度字段,如果小于该数字,则输出该数字长度的字段,除输出规定字段长度以外,其余的空位用空格符填充,如果是左对齐则从右边开始填充,如果是右对齐,则从左边开始填充。可以理解为开辟输出空间。对于*
,它的意思是该字段位数由后边对应顺序的变量决定,如
printf("%*s", max, s)
表示输出指定的s字符串,最小输出位数由max决定;
- .
,作用是分隔最小输出字段数字与精度数字;
- 数字
或*
,代表输出数字或字符串的精度,如果将要输出的字符串小于该数字,则输出字符串实际全部内容,如果大于该数字,则仅输出该数字指定的位数大小,从最右边开始取舍。若输出浮点数,则代表输出小数点后的位数,若输出整型数,则代表最小输出的数字位数。可以理解为直接截取输出字段。*
与上文的相同,如
printf("%.*s", max, s)
表示输出指定的s字符串,输出精度由max决定;
- 字母h或l
,字母h表示将整数作为short类型打印,字母l表示将整数作为long类型打印;另,l
也可以在输出浮点型数据时使用,用于
- s
,代表该格式转换符将转换输出的是字符串。
另,如果%
后边紧接着一个%,则不作为特殊格式来判断,直接打印一个%
。
启动一个C语言程序时,操作系统环境负责打开3个文件,并将这3个文件的指针提供给该程序。这3个文件分别是标准输入、标准输出和标准错误,相应的文件指针分别为stdin
,stdout
,stderr
,它们在<stdio.h>
中声明。在大多数环境中,stdin
指向键盘,而stdout
和stderr
指向显示器,但是,stdin
和stdout
可以被重定向到文件或管道。
要注意,函数int getc(FILE *fp)
,int putc(int c,FILE *fp)
,是对文件的操作。另外,char *gets(char *s)
和int puts(const char *s)
是操作stdin
与stdout
,而对文件中字符串的操作函数是char *fgets(char *line, int maxline, FILE *fp)
和int fputs(char *line, FILE *fp)
。另,gets
函数在读取字符串时将删除结尾的换行符('\n'),而puts
函数在写入字符串时将在结尾添加一个换行符。
函数malloc()和calloc用于动态的分配存储块。函数malloc声明如下:
void *malloc(size_t n)
当分配成功时,它返回一个指针,设指针指向n字节长度的未初始化的存储空间,负责返回NULL。函数calloc的声明如下:
void *calloc(size_t n, size_t size)
当分配成功时,它返回一个指针,该指针指向的空闲空间足以容纳由n个指定长度的对象组成的数组,否则返回NULL。该存储空间被初始化为0.
关于无用变量会导致空指针检查失效的例子。
extern void bar(void);
void foo(int *x)
{
int y = *x; /* 1 */
if(!x) /* 2 */
{
return; /* 3 */
}
bar;
return;
}
该例子中,不管x是否为空指针,函数bar都会被调用,并且程序不崩溃。首先,由于第1句话,编译器会认为x不会是一个空指针,故而第2句话判断x是否为空指针的语句会被认为无用代码优化掉,同时第3句话也不会执行;另外,由于*x并不是volatile类型,所以未使用的 变量y会被编译器直接优化掉,所以函数bar将必定会被执行。
一个指针循环:
int *zp, *xp, *yp;
for(i = 0; i< 10; i++)
{
*zp++ = *xp + *yp;
}
和另外一个指针循环:
int *zp, *xp, *yp;
int tmp = *xp + *yp;
for(i = 0; i< 10; i++)
{
*zp++ = tmp;
}
有什么不同?
答:不同,当指针zp不会与xp或yp指向同一片内存空间时是相同的,一旦zp == xp
或zp == yp
时,结果就不同了。
size_t
类型很重要,他可以存储下任何对象的大小值,所以用他定义数组长度的存储变量很重要。size_t
是函数sizeof
的返回值类型,也是如strlen()
等字符串长度计算函数的返回值类型,在一般机子上是unsigned int
。
注意在函数调用中的逗号符号,如func(a(), b())
,其中的逗号并不是逗号运算符,因此不能保证运算的顺序,a()
和b()
会随机的顺序计算。
无符号整数的上限溢出是确定的,如UINT_MAX+1 = 0
;但有符号整数的上限溢出是不确定的,取决于编译器。
关于函数返回值为指针类型的分析。
char *getstring(void)
{
char p[] = "hello";
return p; /* 编译器一般会提示警告*/
}
void main(void)
{
char *str = NULL;
str = getstring();
printf("%s", str);
}
首先分析,在函数getstring()
中,定义的变量p属于局部变量,当函数调用结束后将释放,所以在主函数中检查返回的指针,是找不到应该指向的内容的。有以下几种解决办法:
(1)使用全局数组;
(2)在函数getstring()
中使用new
或malloc
在堆上动态分配内存,但是需要在主函数中使用了该返回值后,释放内存空间,否则会造成内存泄漏,分别用delete()
free()
释放。使用delete
时,会调用类的析构函数,而free
则不会。
(3)定义为静态类型static char p[] = "hello";
。这样请避免在主函数中多次调用该函数,否则,一个地方的调用修改了该函数中的指针变量,在另外一个地方会受到影响,不是C应该提倡的。
(4)用String类型,会对指针所指向的字符串进行值拷贝,而不是传递内存地址,奈何在C++中才有。
(5)使用字符串常量const char *p = "hello";
。字符串常量存储在静态存储区域,所以,虽然随着函数调用结束,指针p所占用的内存会被释放,但p所指向的字符串常量内存并不会释放,而持续到程序结束,这样,函数返回的地址就是实际存放字符串的地址。如果使用p[]
来定义就不同了,p本身就是字符串,随着函数调用的结束,p这片内存就会被释放,数据也就会丢失。
参考:http://blog.chinaunix.net/uid-20788636-id-1841283.html