☆const top
const表明程序运行期间的值都不会变化,const变量是存放在只读内存中(一般说来,我们的程序指令也被放置在只读内存中)。
例如定义一个常量∏:const double pi=3.1415926;如果后面去修改pi的值会报错,比如pi=pi+1;这样是编译通不过的。
深入讨论一下
1)const int a=10;和int const a=10;是等价的。
2)const int * p;//(*p)是一个常量,但是p的值是可以变化的;
比如:int a=10,b=20;p=&a;……;p=&b;这样是对的,但是*p=20;这样是不行的。
int a=10;int * const p=&a;*p=20;//p是一个常量,而*p的值是可以变化的,这时必须在声明p的时候就给p赋值,因为以后不能改变p的值。
用途:
const的用途比较明显,声明一些自然数据,比如圆周率的值,因为这些值是一个定值。另外还可以放置程序误修改变量的值,比如把某个值传入某个函数,为了放置值被修改,可以把参数用const声明。
☆定义多维数组 top
int a[3][3]={{1,2,3},{4,5,6},{7,8,9}};
int a[3][3]={1,2,3,4,5,6,7,8,9};//和上面的等价
另外c允许我们只申明部分值,未申明的值都初始化为0,例如:
int a[3][3]={{1,2,3},{4},{5,6}};
int a[3][3]={1,2,3,4,0,0,5,6};
int b[3][3]={[0,0]=1,[0,1]=2,[0,2]=3,[1,0]=4,[2,0]=5,[2,1]=6};
//上面三句和int a[3][3]]={{1,2,3},{4,0,0},{5,6,0}};等价。
求数组长度:
#define ArraySize(ARR) (sizeof(ARR) / sizeof(ARR[0]))
☆结构体的初始化 top
1)结构体变量的初始化和数组的初始化很类似,可以用逗号将结构体成员的值分割,并用一对大括号将其包围起来。例如:
struct mydate {int year;int month;int day;};
struct mydate date1 ={2007,7,23};
2)也可以只申明部分值,未申明的值可以为0,也可能为其他值(由编译器决定),例如:
struct mydate date2={2007,7};//相当于{2007,7,0};
3)初始化结构体数组,例如:
struct mydate dates[2]={{2007,7,23},{2007,7,24}};
☆字符串的初始化 top
char a[]="hello";
char b[]={"hello"};
char c[]={'h','e','l','l','o','\0'};
char d[6]="hello";
char e[]="he""llo";//编译器会自动将相邻的字符串连接起来
char f[]="he\
llo";//如果要跨行的时候,可以使用反斜线,但是注意,续行一定要从开始写,不能有空格等。
以上六种方法等价,但是如果写成char d[5]="hello";这样程序不报错,但是明显可能导致灾难。
☆指针与数组 top
数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
int *p;int a[10];
p=a;等价于p=&a[0];
*(p+1);等价于a[1];
使用指针访问数组比使用下标的速度快。
☆字符串常量与指针 top
char a[10];
a="hello";//这样是错误的,因为在申明数组a时,a已经分配了空间,a指向一个固定地址,而"hello"是一个常量,地址也是固定了的,常量不能赋值给常量。但是下面那样是可以的:
char *a;a="hello";
☆宏定义 top
一般定义,后面不要加分号
定义:#define pt1(s) printf(s)
调用:pt1("woxingwosu");
结果:woxingwosu
接受可变参数个数的宏(C99规范中新增的,目前似乎只有gcc支持)
定义:#define pt1() printf(s)
调用:char * p="2007-07-24";pt1("woxingwosu %s",p);
结果:woxingwosu 2007-07-24
#操作符(将参数转变成字符串)
定义:#define str(s) #s
调用:printf(str(woxingwosu));
结果:woxingwosu
##操作符(连接参数)
定义:#define myvar(s) woxingwosu##s
调用:int woxingwosu10=100;printf("value=%d",myvar(10));
结果:100
☆
条件编译 top
(#ifdef,#endif,#else,#ifndef,#if,#elif,#undef)
例如:
//考虑平台的移植性
#ifdef UNIX
#define PATH "/home/root"
#else
#define PATH "c:\windows"
#endif
//避免多次宏定义(一般会报错)
#ifndef PATH
#define PATH "c:\windows"
#endif
//加入判断条件
#define OS 1
……
#if OS==1
#define PATH "c:\windows"
#elif OS==0
#define PATH "/homw/root"
#endif
//取消定义的宏定义
#undef PATH
☆
枚举类型 top
enum Week {sun,mon,tue,wed,thu,fri,sat};//定义一个枚举类型,其实sun~sat的值分别是0~6;
enum Week week1=mon;//赋值
//如果指定其中一个值,后面的值会递增
enum Week {sun,mon=5,tue,wed,thu,fri,sat};
//sun=0,mon~sat=5~10
☆
typedef语句(别名) top
typedef int count//等价于#define count int
count i=5;
_______________________________________________
typedef char Name[20];
Name name="hello";//等价于char name[20]="hello";
_______________________________________________
typedef char * P;
P p="hello";
_______________________________________________
typedef struct{int year;int month} Date;
Date date={2007,7};
☆
static/extern top
(1)静态局部变量在函数内定义,但不象自动变量那样,当调用时就存在,退出函数时就消失。静态局部变量始终存在着,也就是说它的生存期为整个源程序。
(2)静态局部变量的生存期虽然为整个源程序,但是其作用域仍与自动变量相同,即只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。
(3)允许对构造类静态局部量赋初值。若未赋以初值,则由系统自动赋以0值。(而对自动变量不赋初值,则其值是不定的。)
(4) 根据静态局部变量的特点, 可以看出它是一种生存期为整个源程序的量。虽然离开定义它的函数后不能使用,但如再次调用定义它的函数时,它又可继续使用, 而且保存了前次被调用后留下的值。 因此,当多次调用一个函数且要求在调用之间保留某些变量的值时,可考虑采用静态局部变量。虽然用全局变量也可以达到上述目的,但全局变量有时会造成意外的副作用(不可重入问题),因此仍以采用局部静态变量为宜。
(5)若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度。
(6)extern,申明某个变量在另外的一个文件中定义。extern int value;value这个变量在其他文件中定义,value是一个外部全局变量。
☆常用的指针类型 top
1)int *p;//指向整型数据的指针
普遍用法:(把指针与数组联系起来)
int a[3];
p=a;
for(int i=0;i<3;i++)
*(p+i)=i;
2)char * p;//指向字符的指针
普遍用法:(方便操作字符数组或者字符串)
char * p="hello";
3)int **p;//指向指针的指针(非常灵活)
普遍用法:(尤其在函数参数时,相当传引用/传地址)
int a[3];
p=&a;
int i=0;
for(;i<3;i++)
*(*p+i)=i;
也可以实现动态数组,例如
#define N 2
……
char **p;
int i=0;
p=(char**)malloc(sizeof(char *)*N);
*(p+0)=(char *)malloc(sizeof(char)*21);
*(p+1)=(char *)malloc(sizeof(char)*11);
sprintf(*(p+0),"The first One");
sprintf(*(p+1),"Second One");
4)int *p[3];//指针数组
普遍用法:
int a[5],b[5],c[5];
p[0]=a;p[1]=b;p[2]=c;
int i=0,j=0;
for(i=0;i<3;i++)
for(j=0;j<5;j++)
*(p[i]+j)=i+j;
5)int (*p)[3];//指向数组的指针
int a[2][3]={{0,1,2},{3,4,5}};
int (*p)[3];
int i=0,j=0;
p=&a[0];
for(i=0;i<2;i++)
for(j=0;j<3;j++)
printf("a[%d,%d]=%d",i,j,(*(p+i))[j]);
6)int (*p)(void);//指向函数的指针
int show(int arg){
printf("hello\n");
return arg+1;
}
int main(void){
int (*p)(int i);
int result;
p=show;
result=p(10);
printf("result=%d",result);
}
☆
操作文件
top
1)打开文件
语法: FILE *fopen(char *filename, *type);
返回值: 指针地址,如果找不到文件或者失败的话返回空
注意:在打开文件后,需要判断文件打开是否成功,否则NULL指针用于文件操作将产生不可预测的结果。
type的含义如下
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
字符 含义
────────────────────────────
"r" 打开文字文件只读
"w" 创建文字文件只写
"a" 增补, 如果文件不存在则创建一个
"r+" 打开一个文字文件读/写
"w+" 创建一个文字文件读/写
"a+" 打开或创建一个文件增补
"b" 二进制文件(可以和上面每一项合用)
"t" 文这文件(默认项)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2)关闭文件
语法: int fclose(FILE *stream);
返回值: 该函数返回一个整型数。当文件关闭成功时, 返回0, 否则返回一个非零值。
注意:一般在程序运行结束的时候,操作系统会自动关闭打开的文件。然而这样却可能导致严重的后果——丢失数据。因为向文件写数据时,是先将数据输出到缓冲区(在内存中),待数据充满缓冲区后才将整个缓冲区的数据正式输出到磁盘上的文件中,这样做的目的是减少频繁的磁盘读写,提高效率。所以,如果缓冲区内的数据未满就结束程序的运行,就会造成最后留在缓冲区内的数据丢失。而使用fclose函数就可以把缓冲区内最后剩余的数据输出到磁盘文件中,并释放文件指针和有关的缓冲区。
3)常用的几个文件操作函数:
int feof(FILE filePtr) 如果指针已经指向结尾,返回1,否则返回0
int ferror(FILE filePtr) 如果filePtr指向的文件在I/O操作中发生了错误,返回1,否则返回0
int fflush(FILE filePtr) 将内部缓冲区写到文件中,如果成功返回0,否则返回EOF
int fgetc(FILE filePtr) 读取文件中的下一个字符,如果到了结尾,返回EOF
int fputc(int c,FILE filePtr) 写入一个字符c,如果成功返回c,否则返回EOF
char * fgets(char * buffer,int n ,FILE filePtr) 从filePtr指向的文件中读取,直到读入n-1个字符或者遇到换行符,读到的数据存储在字符数组buffer中,如果读取发生错误或者到了文件结尾,返回NULL,否则返回buffer
int fprintf(FILE filePtr,char * format[,argument,]) 和printf差不多,区别在于printf输出到stdout,而fprintf输出到filePtr指向的文件
int fscanf(File filePtr,char * format[,argument,]) 和scanf差不多,和fprintf相对应
size_t fread(void *ptr, size_t size, size_t n, FILE *stream) 参数ptr是保存读取的数据,void*的指针可用任何类型的指针来替换,如char*、int *等等来替换;size是每块的字节数;n是读取的块数,如果成功,返回实际读取的块数(不是字节数),一般用于二进制模式打开的文件中。
FILE * freopen(char * filename,char * accessMode,FILE * filePtr) 关闭filePtr对应的文件,以accessMode中的模式打开filename对应的文件,并将该文件与filePtr指向的文件关联起来。一般用于将标准输入,标准输出和标准错误重定向其他文件。如果操作成功,返回filePtr,否则返回NULL。例如将stderr重定向到文件error.logs,例如freopen("error.logs","w",stderr);
int fseek(FILE * filePtr,int offset,int mode) 重新设置文件指针的位置,offset是移动的字符数,负值表示往首部移动,mode有三种取值:SEEK_SET(从文件开始计算),SEEK_CUR(从当前位置计算),SEEK_END(从文件结束计算)。如果操作成功返回0,否则返回非0值。
void rewind(FILE * filePtr) 将文件指针重新定向到文件的开始
int remove(char * filename) 删除文件名为filename的文件,如果成功返回0,否则返回非0值
int rename(char * oldname,char * newname) 文件重命名,如果成功返回0,否则返回非0值
FILE * tmpfile(void) 创建并以写更新模式打开一个二进制形式的临时文件。如果成功返回指向的文件指针,否则返回NULL。当程序结束时,该临时文件自动删除。
更详细可以参考:http://hi.baidu.com/bhqiang/blog/item/b4caba51a3512f19377abe7f.html
4)另外文本文件和二进制文件的区别
按二进制写文件指的是直接按照数据在内存中的表现形式写入文件。例如,如果int型数据在内存中用4个字节表示,则写这个int数据的时候直接把对应的内存中4个字节的内容写入文件。在此过程中数据不需要做任何转换,所以效率较高。数据有字符型和非字符型(数)两种。按文本方式写文件指的是将数据转换为对应的字符型数据之后再写入文件。对于字符型数据,由于其本身就是ASCII码字符,一般不必转换,直接写入文件。但是,由于不同的系统对于换行符('\n')有不同的处理(转换)方式,在有的系统(如Windows)下也会对'\n'作适当的转换。
对于非字符型数据,都要进行转换处理。例如:int k=11; 如果是文本文件则存储为'1','1'。如果二进制文件,存储为0000000 0000000 0000000 00001011。
5)一个简单操作文件的例子:
#include <stdio.h>
void showContentByChar(FILE *file);
void showContentByLine(FILE *file);
int main(){
FILE * inputFile;
int c;
inputFile=fopen("woxingwosu.data","rb");
if(inputFile==NULL){
printf("找不到文件");
return -1;
}
printf("-----按字符读:-------\n");
showContentByChar(inputFile);
rewind(inputFile);//指针回到文件首
printf("\n-----按行读:-------\n");
showContentByLine(inputFile);
fclose(inputFile);
return 0;
}
//按字符读文件
void showContentByChar(FILE *file){
int c;//最好定义成int,不要定义为char
if(file==NULL){
printf("文件指针为空!");
return ;
}
while((c=getc(file))!=EOF)
putchar(c);
}
//按行读文件
void showContentByLine(FILE *file){
int N=512;
char buff[N+1];
if(file==NULL){
printf("文件指针为空!");
return ;
}
while((fgets(buff,N,file))!=NULL)
printf("%s",buff);
}
☆
系统预定义符号 top
有些符号是系统自定义了的
————————————————————————————————————————————————
__FILE__ 当前文件名
__LINE__ 当前行号
__DATE__ 当前日期(月日年)
__TIME__ 当前时间(时分秒)
__func__ 当前函数(注意是小写)
————————————————————————————————————————————————
我们可以利用这些来记录日志,例如:
#include <stdio.h>
#ifndef ERROR_LOG
#define ERROR_LOG printf("[%s %s][%s %s line:%d] ",__DATE__,__TIME__,__FILE__,__func__,__LINE__)
#endif
int main(){
printf("begin\n");
ERROR_LOG;
printf("end");
getchar();
}
☆
sizeof top
sizeof不是一个函数,而是一个一元运算符。
sizeof看似简单,实际上有时让人头痛。先看下面的程序,猜猜程序的结果
#include <stdio.h>
int main(){
char a='a';
char * b="hello";
char c[10]="hello";
struct Data1{char m;int n};
struct Data2{char m;struct Data1 data1;int n};
printf("sizeof(a)=%ld\n",sizeof(a));
printf("sizeof(b)=%ld\n",sizeof(b));
printf("sizeof(* b)=%ld\n",sizeof(*b));
printf("sizeof(c)=%ld\n",sizeof(c));
printf("sizeof(Data1)=%ld\n",sizeof(struct Data1));
printf("sizeof(Data2)=%ld\n",sizeof(struct Data2));
}
结果如下:
sizeof(a)=1
sizeof(b)=4
sizeof(* b)=1
sizeof(c)=10
sizeof(Data1)=8
sizeof(Data2)=16
解释一下,sizeof(b),b是一个指针,长度为4字节;sizeof(* b),*b是一个字符,长度为1个字节;sizeof(c),c被分配了10个字节的空间;sizeof(struct Data1),结构体会字节对齐,即按照最长的基本数据类型来递增空间,这样每个基本数据数据类型的偏移地址都是最长基本数据类型的n倍,这样可以加快计算机的取数速度,减少指令周期。对于长度比最长基本数据类型短的类型,在后面填充字节。这样sizeof(struct Date1)就为size(int)*2;sizeof(struct Data2),其实Data2会把Data1打算,最后还是按int填充,所有为sizeof(int)*4。
☆
malloc与calloc的区别 top
函数malloc()和calloc()都可以用来动态分配内存空间,但两者稍有区别。
语法格式:
void*malloc(size_tsize);
void*calloc(size_tnumElements,size_tsizeOfElement);
malloc()函数有一个参数,即要分配的内存空间的大小:
calloc()函数有两个参数,分别为元素的数目和每个元素的大小,这两个参数的乘积就是要分配的内存空间的大小。
如果调用成功,函数malloc()和函数calloc()都将返回所分配的内存空间的首地址。
函数malloc()和函数calloc() 的主要区别是前者不能初始化所分配的内存空间,而后者能。如果由malloc()函数分配的内存空间原来没有被使用过,则其中的每一位可能都是0;反之, 如果这部分内存曾经被分配过,则其中可能遗留有各种各样的数据。也就是说,使用malloc()函数的程序开始时(内存空间还没有被重新分配)能正常进行,但经过一段时间(内存空间还已经被重新分配)可能会出现问题。
函数calloc() 会将所分配的内存空间中的每一位都初始化为零,也就是说,如果你是为字符类型或整数类型的元素分配内存,那麽这些元素将保证会被初始化为0;如果你是为指 针类型的元素分配内存,那麽这些元素通常会被初始化为空指针;如果你为实型数据分配内存,则这些元素会被初始化为浮点型的零。
calloc(m, n) 本质上等价于p = malloc(m * n); memset(p,0, m * n);显然calloc的效率要比malloc低,如果没有初始为0的要求就用malloc。
总之,当你在calloc()函数和malloc()函数之间作选择时,你只需考虑是否要初始化所分配的内存空间。
另外还有一个重新分配内存的函数realloc
语法格式:void * realloc(void * p,size_t size); 改变指针p分配的空间,重新设置为size字节。返回新分配空间的地址,如果失败返回NULL。原则上是先释放再重新分配的。但是好像跟系统有关系。如果缩小空间,可能会复制空间。如果变大空间,那么返回的指针不一定就是原来的指针了。
☆
内存函数 top
头文件:<string.h>
void *memchr(void *s, char ch, unsigned n);在数组的前n个字节中搜索字符ch。如果找到,返回指向该位置的指针,否则返回NULL
int memcmp (const void *s1,const void *s2,size_t n);比较s1和s2所指的内存区间前n个字符。字符串大小的比较是以ASCII码表上的顺序来决定,次顺序亦为字符的值。memcmp()首先将s1第一个字符值减去s2第一个字符的值,若差为0则再继续比较下个字符,若差值不为0则将差值返回。若参数s1和s2所指的内存内容都完全相同则返回0值。s1若大于s2则返回大于0的值。s1若小于s2则返回小于0的值。
void * memcpy (void * dest ,const void *src, size_t n);拷贝src所指的内存内容前n个字节到dest所指的内存地址上。与strcpy()不同的是,memcpy()会完整的复制n个字节,不会因为遇到字符串结束\0而结束。返回指向dest的指针。注意:指针src和dest所指的内存区域不可重叠。
void * memmove(void *dest,const void *src,size_t n);memmove()与memcpy()一样都是用来拷贝src所指的内存内容前n个字节到dest所指的地址上。不同的是,当src和dest所指的内存区域重叠时,memmove()仍然可以正确的处理,不过执行效率上会比使用memcpy()略慢些。返回指向dest的指针。
void * memset (void *s ,int c, size_t n);将参数s所指的内存区域前n个字节以参数c填入,然后返回指向s的指针。在编写程序时,若需要将某一数组作初始化,memset()会相当方便。返回指向s的指针。注意:参数c虽声明为int, 但必须是unsigned char ,所以范围在0到255之间。
int sprintf(char * buffer,char * format[,argument]); 跟printf差不多,可以参考前面的fprintf(),常用于类型转换。转换结束后会自动加上空字符\0。返回放置在buffer中的字符数(不包括结束的空字符)。头文件。
int sscanf(char * buffer,char * format[,argument]); 跟scanf()差不多,返回成功转换参数的个数。例如:char buffer[]="woxingwosu 666";char username[15];int value;sscanf(buffer,"%s %d",username,&value)头文件
☆符号重载
static:
在函数内部,表示该变量的值在各个调用间都一直保持延续性(注意不可重入的问题)
用于函数,表示该函数只对本文件可见
extern
用于变量定义,表示该变量在其他地方定义
用于函数定义,表示全局可见(属于多余)
void
作为函数的返回类型,表示不返回任何值
在指针声明中,表示通用指针的类型
位于参数列表中,表示没有参数
☆volatile
volatile的本意是“易变的”
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:
static int i=0;
int main(void)
{
while (1)
{
if (i) dosomething();
}
}
/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}
程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被调用。如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义;
【http://www.laogu.com/wz_692.htm】
posted on 2007-08-17 14:48
破茧而出 阅读(1075)
评论(1) 编辑 收藏 所属分类:
C/C++