C学习扎记

来源:互联网 发布:换手率选股软件 编辑:程序博客网 时间:2024/04/30 21:12

类型与变量:
用双引号引起的字符串,代表的是一个指向无名数组起始字符的指针,该数组被双引号之间的字符以及一个额外的二进制为零的字符’\0’初始化。
一旦知道了如何声明一个给定类型的变量,那么该类型的类型转换就很容易得到了:只需要把声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个“封装”起来即可。
任何C变量的声明都由两部分组成:类型以及一组类似表达式的声明符。声明符从表面上看与表达式类似,对它求值应该返回一个声明中给定类型的结果。
例:(*(void(*)())0)();启动时,硬件将调用首地址为0位置的子例程。分析:
1.void(*fp)()表示fp为指向返回值为void型函数的指针;
2.(void(*)())表示指向返回值为void的函数指针”的类型转换符;
3.(void(*)())0表示将常数0转型为“指向返回值为void的函数的指针”类型
4.(*(void(*)())0)();表示调用一个返回值为void的函数,调用方式为把0中的内容看作是一个指向返回值为void函数指针,然后通过该指针来调用。
5. 实际中可以这样使用:typedef void (*fp)();  (*(fp)0)();

ANSI C规定数组定义时长度必须是“常量”,“只读变量”也是不可以的,即数据大小不能由const修饰的变量来指定。但标准C++中则是可以的.

关键字
Typedef声明有助于创建平台无关类型,甚至能隐藏复杂和难以理解的语法,从而增强可移植性和以及未来的可维护性。
例 typedef int size; 此声明定义了一个 int 的同义字,名字为 size。 typedef struct tagMyStruct {int iNum; long lLength;} MyStruct;   定义一个新的结构类型,并为这个新的结构起了一个名字叫MyStruct
含有指针要时要特别注意,typedef char * pstr;  但是const pstr p1 将会被解释为‘char* const‘(p1是一个指向 char 的常量指针),而不是‘char *(指向char 的指针)。应该使用用typedef const char* cpstr;

注意:1不管什么时候,只要为指针声明 typedef,那么都要在最终的 typedef 名称中加一个 const,以使得该指针本身所指对象不会通过指针被修改。2.和#define不同,typedef不是简单的文本替换。

CONST:限定一个变量不允许被改变。使用const在一定程度上可以提高程序的安全性和可靠性
使用的基本形式: const char m; 限定m不可变。


C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。

运算符:
真正意义的运算符中,单目运算符优先级最高,自右至左结合。接下来是双目运算符,其中算术运算符最高,移位运算符次之,关系运算符再次之,然后是逻辑运算符,赋值运算符,最后是条件运算符。
C语言中只有四个运算符(&&、||、?:、,)存在规定的求值顺序。其他所有运算对其操作数求值的顺序是为定义的,特别地,赋值运算符并不保证任何求值顺序。
当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。

函数:
函数定义中不能省略参数类型的说明。
如果一个函数在被定义或声明之前被调用,那么它的返回类型就默认为整型。
函数的形式参数不能声明为静态,因为实参总是在堆栈中传递给函数,用于支持递归。

库函数:
库函数strlen返回参数中的字符串所包括的字符数目,作为结束标志的空字符并未计算在内。
对比sizeof:
sizeof包括结束标志的空字符。

sizeof是编译期间计算的(操作符),测量这个变量占用的内存大小; 返回值是unsign char 类型
strlen是运行期间计算的(函数),测量这个变量实际字符个数(需要通过'/0'来判读结束)。


编译与连接:
连接器通常把目标模块看成是一个由一组外部对象(external object)组成的,每个外部对象代表着机器内存中的某个部分,并通过一个外部名称来识别。程序中的每个函数和每个外部变量,如果没有被声明为static,就是一个外部对象。

编译器可以确定4种不同类型的作用域--文件作用域,函数作用域,代码作用域和原型作用域。

程序的内存分配:
一个由C/C++编译的程序占用的内存分为以下几个部分 
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。 
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后由系统释放。 
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码

例子程序(一个前辈写的,非常详细)  
//main.cpp  
int a = 0; 全局初始化区  
char *p1; 全局未初始化区  
main()  
{  
int b; 栈  
char s[] = "abc"; 栈  
char *p2; 栈  
char *p3 = "123456"; 123456/0在常量区,p3在栈上。  
static int c =0; 全局(静态)初始化区  
p1 = (char *)malloc(10);  
p2 = (char *)malloc(20);  
分配得来得10和20字节的区域就在堆区。  
strcpy(p1, "123456"); 123456/0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。  
  
    
堆和栈的理论知识  
申请方式  

stack: 由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间;heap: 需要程序员自己申请,并指明大小,在c中malloc函数,如p1 = (char *)malloc(10);  
在C++中用new运算符如p2 = new char[10];   但是注意p1、p2本身是在栈中的。 
  

申请后系统的响应  
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。 
申请大小的限制  
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。  
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
  
申请效率的比较:  
栈由系统自动分配,速度较快。但程序员是无法控制的。堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈,是直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。
堆和栈中的存储内容  
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。  堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。  
   
存取效率的比较      
char s1[] = "aaaaaaaaaaaaaaa";  
char *s2 = "bbbbbbbbbbbbbbbbb";  
aaaaaaaaaaa是在运行时刻赋值的;  
而bbbbbbbbbbb是在编译时就确定的;  
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。  
比如:  
#include  
void main()  
{  
char a = 1;  
char c[] = "1234567890";  
char *p ="1234567890";  
a = c[1];  
a = p[1];  
return;  
}  
对应的汇编代码  
10: a = c[1];  
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]  
0040106A 88 4D FC mov byte ptr [ebp-4],cl  
11: a = p[1];  
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]  
00401070 8A 42 01 mov al,byte ptr [edx+1]  
00401073 88 45 FC mov byte ptr [ebp-4],al  
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,再根据edx读取字符,显然慢了. 


文件:
文件通常就是磁盘上的一段命名的存储区


预编译:

宏的解开不象函数执行,由里带外,而是直接替换
#line 指令用于重置由__LINE__和__FILE__宏报告的行号和文件号。
#error 指令使预处理器发出一条错误消息,该消息包含指令中的文本。
## 连接符:将两个Token连接为一个Token。注意这里连接的对象是Token就行,而不一定 是宏的变量
#符:将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号
MSDN上的一个例子:
假设程序中已定义了一个带参数的宏   #define paster( n ) printf( "token" #n " = %d", token##n ) 
又定义了一个整形变量                        int token9 = 9; 
在主程序中以下面的方式调用这个宏   paster( 9 );
那么在编译时,这句话被扩展为:      printf( "token" "9" " = %d", token9 );

原创粉丝点击