c语言知识整理(一) 关键字部分
1. C语言标准定义的32个关键字
| 关键字 | 意义 | 特点 |
| auto | 声明自动变量,缺省时编译器一遍默认为auto | |
| int | 声明整型变量 | 4byte |
| double | 声明双精度变量 | 8byte |
| long | 声明长整型变量 | 4byte |
| char | 声明字符型变量 | 1byte |
| float | 声明浮点型变量 | 4byte |
| short | 声明短整型变量 | 2byte |
| signed | 声明有符号类型变量 |
int main() { char a[1000]; int i; for(i=0; i<1000; i++) { a[i] = -1-i; } printf("%d",strlen(a)); return 0; } 答案是255 for 循环内,当i 的值为0 时,a[0] 的值为-1 。关键就是-1 在内存里面如何存储。 提出的问题还不懂 1.-0 和+0在内存里面分别怎么存储? 2. int i = -20; unsigned j = 10; i+j=-10 3.下面的代码有啥问题? unsigned i ; for (i=9;i>=0;i--) { printf("%u\n",i); }
|
| unsigned | 声明无符号类型变量 | |
| struct | 声明结构体变量 |
struct 是个神奇的关键字,它将一些相关联的数据打包成一个整体,方便使用。 结构体所占的内存大小是其成员所占内存之和(关于结构体的内存对齐,请参考预处 struct student { }stu; sizeof(stu)的值是多少呢?在Visual C++ 6.0上测试一下。 很遗憾,不是0 ,而是1。编译器认为任何一种数据类型都有其大小,用它来定义一个变量能够分配确定大小的空间。那万一结构体真的为空,它的大小为什么值比较合适呢?假设结构体内只有一个char 型的数据成员,那其大小为 1byte(这里先不考虑内存对齐的情况). 也就是说非空结构体类型数据最少需要占一个字节的空间,而空结构体类型数据总不能比最小的非空结构体类型数据所占的空间大吧。这就麻烦了,空结构体的大小既不能为0,也不能大于 1 ,怎么办?定义为 0.5个byte ?但是内存地址的最小单位是1 个byte ,0.5个byte 怎么处理?解决这个问题的最好办法就是折中,编译器理所当然的认为你构造一个结构体数据类型是用来打包一些数据成员的,而最小的数据成员需要1 个byte ,编译器为每个结构体类型数据至少预留1 个byte的空间。所以,空结构体的大小就定位1 个byte 。 柔性数组(flexible array) c99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员,但结构中的柔性数组成员前面必须至少一个其他成员。柔性数组成员允许结构中包含一个大小可变的数组。sizeof 返回的这种结构大小不包括柔性数组的内存。包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。 柔性数组到底如何使用呢?看下面例子: typedef struct st_type { int i; int a[0]; }type_a; 有些编译器会报错无法编译可以改成: typedef struct st_type { int i; int a[]; }type_a; 这样我们就可以定义一个可变长的结构体,用 sizeof(type_a) 得到的只有4 ,就是sizeof(i)=sizeof(int)。那个 0 个元素的数组没有占用空间,而后我们可以进行变长操作了。通过如下表达式给结构体分配内存: type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int)); 这样我们为结构体指针p 分配了一块内存。用p->item[n]就能简单地访问可变长元素。 但是这时候我们再用sizeof(*p)测试结构体的大小,发现仍然为4。是不是很诡异?我们不是给这个数组分配了空间么? 别急,先回忆一下我们前面讲过的“模子”。在定义这个结构体的时候,模子的大小就已经确定不包含柔性数组的内存大小。柔性数组只是编外人员,不占结构体的编制。只是说在使用柔性数组时需要把它当作结构体的一个成员,仅此而已。再说白点,柔性数组其实与结构体没什么关系,只是“挂羊头卖狗肉”而已,算不得结构体的正式成员。 需要说明的是:C89 不支持这种东西,C99 把它作为一种特例加入了标准。但是,C99所支持的是incomplete type ,而不是zero array,形同int item[0]; 这种形式是非法的,C99 支持的形式是形同int item[]; 只不过有些编译器把int item[0]; 作为非标准扩展来支持,而且在C99 发布之前已经有了这种非标准扩展了,C99 发布之后,有些编译器把两者合而为一了。当然,上面既然用malloc 函数分配了内存,肯定就需要用free 函数来释放内存: free(p); 经过上面的讲解,相信你已经掌握了这个看起来似乎很神秘的东西。不过实在要是没掌握也无所谓,这个东西实在很少用。
在C++ 里struct 关键字与class 关键字一般可以通用,只有一个很小的区别。struct 的成员默认情况下属性是public 的,而 class 成员却是private 的。 |
| union | 声明联合数据类型 |
union 维护足够的空间来置放多个数据成员中的“一种” ,而不是为每一个数据成员配置 |
| enum | 声明枚举类型 |
枚举与#define宏的区别: |
| static | 声明静态变量 |
静态变量作用范围在一个文件内,程序开始时分配空间,结束时释放空间,默认初始化为0,使用时可以改变其值。 静态变量或静态函数只有本文件内的代码才能访问它,它的名字在其它文件中不可见。 1.修饰变量。变量分为局部和全局变量,它们都存在内存的静态去。 1.1静态全局变量,作用域仅限于变量被定义的文件中,其他文件即使使用extern声明也无法使用他。 1.2静态局部变量,在函数体里面定义的,就只能在这个函数里使用了,同一个文档中的其他函数用不了,由于被static修饰的变量总是存在内存的静态区,所以即使这个函数运行结束,这个静态变量的值还是不会被销毁,函数下次使用仍然能用到这个值。
2修饰函数:函数前加static使得函数成为静态函数,但此处"static"的含义并不是指存储方式,而是指对函数的作用域仅局限于本文件(内部函数)。好处在于:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其他文件中的函数同名。同时可作为对象间的一种通信机制。 作用一:保持变量内容的持久 存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。
作用二:隐藏 不加static前缀的全局变量和函数都具有全局可见性,举例我们同时编译两个原文件"test.c" 和 "main.c",但是这样对函数的使用在cpp中不太行。 如果加了static,就会对其他源文件隐藏。利用这一特性可以在不同的文件中定义同名函数和同名变了,而不必担心命名冲突。 作用三:默认初始化为0 全局变量也具备这一属性,因为全局变量也存储在静态数据区。 在静态数据区,内存中所有的字节默认值都是0x00。 #include <cstdio> #include <iostream> using namespace std; int test; int main() { static char str[5]; printf("integer: %d; string:(begin)%s(end)",test,str); return 0; } output: integer: 0; string: (begin)(end)
3c++中的:静态成员和静态成员函数 Tips: 函数前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。 |
| switch | 用于开关语句 | case 后面只能是整型或字符型的常量或常量表达式 把default 子句只用于检查真正的默认情况。 |
| case | 开关语句分支 | |
| default | 开关语句中的"其他"分支 | |
| break | 跳出当前循环 | |
| register | 声明寄存器变量 |
1.请求编译器尽可能的将变量存在CPU内部寄存器中而不是通过内存寻址访问以提高效率 2.register变量必须是一个单个的值,并且其长度应小于或等于整型的长度。而且register变量可能不存放在内存中,所以不能用取址运算符\'&\'来获取寄存器变量的地址 |
| const | 声明只读变量 |
1const 修饰的只读变量,具有不可变性 const int Max=100; int Array[Max]; 这里请在Visual C++6.0 里分别创建.c 文件和.cpp 文件测试一下。你会发现在.c 文件中,编译器会提示出错,而在.cpp 文件中则顺利运行。为什么呢?我们知道定义一个数组必须指定其元素的个数。这也从侧面证实在 C 语言中,const修饰的Max 仍然是变量,只不过是只读属性罢了;而在C++ 里,扩展了const的含义. 2节省空间,避免不必要的内存分配,同时提高效率 编译器通常不为普通const只读变量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的值,没有了存储与读内存的操作,使得它的效率也很高。 #define M 3 //宏常量 const int N=5; // 此时并未将N 放入内存中 ...... int i=N; // 此时为N 分配内存,以后不再分配! int I=M; // 预编译期间进行宏替换,分配内存 int j=N; // 没有内存分配 int J=M; // 再进行宏替换,又一次分配内存! const 定义的只读变量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以const定义的只读变量在程序运行过程中只有一份拷贝(因为它是全局的只读变量,存放在静态区),而#define定义的宏常量在内存中有若干个拷贝,#define宏是在预编译阶段进行替换,而const修饰的只读变量是在编译的时候确定其值。#define宏没有类型,而const修饰的只读变量具有特定类型 修饰指针 const int *p;//p可变,p指向的对象不可变 int const *p; //p可变,p指向的对象不可变 int *const p; //p不可变,p指向的对象可变 const int * const p;//指针p和p指向的对象都不可变 记忆方法:先忽略类型名(编译器解析的时候也是忽略类型名),我们看 const离哪个近 const char *aa = "hello"; aa++; // YES aa[0] = \'w\'; // compile error char* const aa = "heo"; aa++; // compile error aa[0] = \'w\'; // ok in compile const修饰符也可以修饰函数的参数,当不希望这个参数值被函数体内意外改变时使 用。例如: |
| volatile | 说明变量在程序执行中可被隐含地改变 |
volatile关键字和const一样是一种类型修饰符,用它修饰的变量表示可以被某些编译器 先看看下面的例子: int i=10; int j = i ;//(1) 语句 int k = i;//(2) 语句 这时候编译器对代码进行优化,因为在(1 )、(2)两条语句中,i 没有被用作左值。这时候编译器认为i 的值没有发生改变,所以在(1 )语句时从内存中取出 i 的值赋给j 之后,这个值并没有被丢掉,而是在(2)语句时继续用这个值给 k 赋值。编译器不会生成出汇编代码重新从内存里取i 的值,这样提高了效率。但要注意:(1 )、(2)语句之间i 没有被用作左值才行。 再看另一个例子: volatile int i=10; int j = i ;//(3) 语句 int k = i;//(4) 语句 volatile关键字告诉编译器i 是随时可能发生变化的,每次使用它的时候必须从内存中取出i的值,因而编译器生成的汇编代码会重新从i 的地址处读取数据放在k 中。这样看来,如果i 是一个寄存器变量或者表示一个端口数据或者是多个线程的共享数据,就容易出错,所以说volatile可以保证对特殊地址的稳定访问。
|
| typedef | 用以给数据类型取别名 |
typedef的真正意思是给一个已经存在的数据类型(注意:是类型不是变量)取一个别 E),#define INT32 int unsigned INT32 i = 10 ; F ),typedef int int32 ; unsigned int32 j = 10; 其中F) 编译出错,为什么呢?E)不会出错,这很好理解,因为在预编译的时候 INT32被替换为int,而unsigned int i = 10 ;语句是正确的。但是,很可惜,用typedef取的别名不支持这种类型扩展。 #define宏有关的例子: G),#define PCHAR char* PCHAR p3,p4; H ),typedef char* pchar; pchar p1,p2; 两组代码编译都没有问题,但是,这里的p4却不是指针,仅仅是一个 char 类型的字符。 这种错误很容易被忽略,所以用#define的时候要慎之又慎。
|
| extern | 声明变量是在其他文件中声明(也可以看做是引用变量) |
extern 可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中 |
| return | 子程序返回语句 |
return 用来终止一个函数并返回其后面跟着的值。 return 语句不可返回指向“栈内存”的“指针”,因为该内存在函数体结束时 |
| void | 声明函数无返回值或无参数,声明空类型指针 |
void 真正发挥的作用在于: 任何类型的指针都可以直接赋值给它,无需进行强制类型转换: 如果函数的参数可以是任意类型指针,那么应声明其参数为void * |
| continue | 结束当前循环,开始下一轮循环 | |
| do | 循环语句的循环体 | |
| while | 循环语句的循环条件 | |
| if | 条件语句 |
针对bool型 if(bTestFlag); if(!bTestFlag);这些写法最好! 针对float型 if((fTestVal >= -EPSINON) && (fTestVal <= EPSINON)); //EPSINON 为定义好的 针对指针型 if(NULL == p); if(NULL != p);这种用法好 |
| else |
条件语句否定分支(与if连用) |
|
| for | 一种循环语句 | |
| goto | 无条件跳转语句 | 一般来说,编码的水平与goto 语句使用的次数成反比。 |
| sizeof | 计算对象所占内存空间大小 |
在计算变量所占空间大小时,括号可以省略,而当计算类型大小时不能省略。因为类型前加关键字会被误认为类型扩展引起歧义。 int *p=NULL; sizeof(p) 4 sizeof(*p) 4 int a[100]; sizeof(a) 400 sizeof(a[100]) 4 sizeof(&a) 4 sizeof(&a[0]) 4 int b[100]; void fun(int b[100]) { sizeof(b);//=4 }
|
2什么是定义?
定义就是(编译器)创建一个对象,为这个对象分配一块内存,并给他取一个名字(变量名或对象名)。一个变量或对象在一定的区域内只能被定义一次,如果定义多次,编译器会提示你重复定义同一个变量或对象。
3什么是声明?
一:告诉编译器,这个名字已经匹配到一块内存上了,下面的代码用到变量或对象是在别的地方定义的。声明可以出现多次。
二:告诉编译器,这个名字已经被使用了,别的地方最好不要再使用这个名字。
4定义和声明最重要的区别?
定义创建了对象并为这个对象分配了内存,声明没有分配内存。
5全局变量、静态变量、静态局部变量和局部变量的区别?
变量可以分为:全局变量、静态全局变量、静态局部变量和局部变量
1.按存储区域分,全局变量、静态全局变量和静态局部变量都存放在内存的静态存储区域,局部变量存放在内存的栈区。
2.按作用域分, 全局变量在整个工程文件内都有效;静态全局变量只在定义它的文件内有效;静态局部变量只在定义它的函数内有效,只是程序仅分配一次内存,函数返回后,该变量不会消失;局部变量在定义它的函数内有效,但是函数返回后失效。
全局变量(外部变量)的说明之前再冠以static就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。
可见,把局部变量改为静态变量是改变了它的存储方式即改变了它的生存期,把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。
(1) static 函数与普通函数作用域不同。仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件
(2) static全局变量与普通的全局变量有什么区别:static全局变量只初始化一次,防止在其他文件单元中被引用;
(3) static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;
(4) static函数与普通函数有什么区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝.
(5) 全局变量和静态变量如果没有手工初始化,则由编译器初始化为0。局部变量的值不可知。
静态变量都在全局数据区分配内存,包括后面将要提到的静态局部变量。对于一个完整的程序,在内存中的分布情况如下表:
| 代码区 |
| 全局数据区 |
| 堆区 |
| 栈区 |
一般程序的由new产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。
6怎么样的变量命名规则是好的?
一直观且可拼读,一看就知道啥意思。 二长度应符合"min-length&&max-information"原则 三当标识符由多个词组成时,每个词的第一个字母大写,其余小写
四尽量避免名字中出现数字编号。五队在多个文件之间共同使用的全局变量或函数要加范围限定符。六标识符名分为两部分:规范标识符前缀(后缀)+含义标识。非全局变量可以不用使用范围限定符前缀。六程序中不得出现仅靠大小写区分的相似的标识符。七一个函数名禁止被用于其他之处。八所有宏定义、枚举常数、只读变量全用大写字母命名,用下划线分割单词。八定义变量的同时不要忘了初始化。九不同类型数据之间的运算要注意精度扩展问题。
| No | 标识符类型 | 作用域前缀 |
| 1 | Global Variable | g |
| 2 | File Static Variable | n |
| 3 | Function Static Variable | f |
| 4 | Auto Variable | a |
| 5 | Global Function | g |
| 6 | Static Function | n |
7循环注意
一.在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数。
二.建议 for 语句的循环控制变量的取值采用“半开半闭区间”写法。
部分内容引用自 http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777441.html
部分内容引自 c语言深度剖析一书
mark 一下该博客的const 用法 http://blog.csdn.net/Eric_Jo/article/details/4138548