(6). goto、void、extern、sizeof
-
goto(跳转,禁用)
-
高手潜规则:禁用goto
-
项目经验:程序质量与goto的出现次数成反比
-
最后的判决:将goto打入冷宫
#include <malloc.h> void func(int n) { int* p = NULL; if( n < 0 ) { goto STATUS; //内存泄漏 } P = (int*)malloc(sizeof(int) * n); STATUS: p[0] = n; free(p); }
-
-
void (表示无)
-
void修饰函数返回值和参数(仅表示无)
-
如果函数没有返回值,那么应该将其声明为void型
-
如果函数没有参数,应该声明其参数为void
-
-
不存在void变量
C语言没有定义void究竟是多大内存的别名(没有void的标尺无法在内存中裁剪出void对应的变量)
#include <stdio.h> int main() { printf("%d\n", sizeof(void)); return 0; }
小贴士:
上述代码在 ASNI C 编译器中是无法通过编译,但在支持 GNU 标准的 gcc 编译器而言是合法的 -
void指针的意义:
-
C语言规定只有相同类型的指针才可以相互赋值
-
void*指针作为左值用于“接收”任意类型的指针
-
void*指针作为右值赋值给其它指针时需要强制类型转换
#include <stdio.h> void* my_memset(void* p, char v, int size) { void* ret = p; char* dest = (char*)p; int i = 0; for(i = 0; i < size; i++) { dest[i] = v; } return ret; } int main() { int a[5] = {1, 2, 3, 4, 5}; int i = 0; long l = 10000; for( i = 0; i < 5; i++) { printf("a[%d] = %d\n", i, a[i]); } my_memset(a, 0, sizeof(a)); for( i = 0; i < 5; i++) { printf("aset[%d] = %d\n", i, a[i]); } printf("l = %d\n", l); my_memset(&l, 0, sizeof(l)); printf("lset = %d\n", l); return 0; }
-
-
-
extern(声明外部定义)
-
extern用于声明外部定义的变量和函数
extern变量在文件的其他地方定义和分配空间
// s2.c int g = 100; int get_min(int a, int b) { return (a < b)? a : b; }
// extern.c #include <stdio.h> extern int g; extern int get_min(int a, int b); int main() { printf("%d\n", g); printf("%d\n", get_min(1, 5)); return 0; }
-
-
extern用于“告诉”编译器用C方式编译
C++编译器和一些变种C编译器默认会按“自己”的方式编译函数和变量,通过extern关键可以命令编译器“以标准C方式进行编译”。
extern "C" { int f(int a, int b) { return a + b; } }
-
sizeof(“计算”大小)
-
sizeof 是编译器的内置指示符,不是函数
-
在编译过程中所有的 sizeof 将被具体的数值所替换
-
程序的执行过程与 sizeof 没有任何关系
-
-
sizeof 用于“计算”相应实体所占的内存大小
-
sizeof用于类型: sizeof(type)
-
sizeof用于变量:sizeof(var) 或 sizeof var
-
-
sizeof 的值在编译期就已经确定
#include <stdio.h> int f() { printf("\n"); return 0; } int main() { int var = 0; int size = sizeof(var++); printf("var = %d, size = %d\n", var, size); size = sizeof(f()); printf("size = %d\n", size); return 0; }
-
(7). const、volatile
-
const(定义的不是真的常量)
“const”含义是“请做为常量使用”,而并非“放心吧,那肯定是个常量”。在现在C语言编译器中,将const修饰的全局生命周期的变量(全局变量、static修饰的变量)存储于只读存储区中,修改将会导致程序崩溃,而标准C语言编译器则存储于可修改的全局数据区,其值依然可以改变。
-
const 修饰变量
-
在C语言中const修饰的变量是只读的(不能作为左值),其本质还是变量
-
本质上const只对编译器有用,在运行时无用
-
const修饰的局部变量在栈上分配空间,修饰的全局变量在全局数据区分配空间,故可以通过指针改变值
#include <stdio.h> const int bb = 0; int main() { const int cc = 1; int* p = (int*)&cc; int* p1 = (int*)&bb; printf("cc = %d\n", cc); cc = 3; //error printf("cc = %d\n", cc); *p = 3; printf("cc = %d\n", cc); *p1 = 3; //error,存储于只读存储区 printf("cc = %d\n", cc); return 0; }
-
-
-
const修饰数组
-
在C语言中const修饰的数组是只读的
-
const修饰的数组空间不可被改变(针对现在C编译器而言[程序会死掉],标准C是可以通过指针改变的)
-
-
const修饰指针
-
const int* p; p可变,p指向的内容不可变
-
int const* p; p可变,p指向的内容不可变
-
int* const p; p不可变,p指向的内容可变
-
const int* const p; p 和 p指向的内容都不可变
口诀:左数右指
当const出现在*号左边时指针指向的数据为常量
当const出现在*后右边时指针本身为常量
左数:
#include <stdio.h> int main() { int i = 0, t = 0; const int* p = &i; int const* y = &t; *p = 3; *y = 3; return 0; }
左数右指:
#include <stdio.h> int main() { int i = 0; const int* const p = &i; *p = 3; p = NULL; return 0; }
-
-
const修饰函数参数和返回值
-
const修饰函数参数表示在函数体内不希望改变参数的值
-
const修饰函数返回值表示返回值不可改变,多用于返回指针的情形
小贴士:
C语言中字符串字面量存储于只读存储区中,在程序中需要使用 const char* 指针。#include <stdio.h> const char* f(const int i) { i = 5; // error return "Delphi Tang"; } int main() { char* pc = f(0); printf("%s\n", pc); pc[6] = '_'; //error printf("%s\n", pc); return 0; }
-
-
volatile(防止优化)
“volatile”的含义是“请不要做没谱的优化,这个值可能变掉的”,而并非“你可以修改这个值”。
-
volatile 可理解为“编译器警告指示字”,用于告诉编译器必须每次去内存中取变量值
-
volatile 主要修饰可能被多个线程访问的变量,也可以修饰可能被未知因数更改的变量
int obj = 10; int a = 10; int b = 0; a = obj; sleep(100); b = obj;
编译器在编译的时候发现 obj 没有被当成左值使用,因此会“聪明”的直接将 obj 替换成 10,把 a 和 b 都赋值为10,如果程序运行到 sleep(100); 处,硬件中断改变了内存中 obj 的值,而 b = obj; 没从内存取值,会使程序出现错误。
-
-
const volatile int i = 0; 这个时候i具有什么属性?编译器如何处理这个变量?
答:没问题,const 和 volatile 这两个类型限定符不矛盾。const表示(运行时)常量语义:被 const 修饰的对象在所在的作用域无法进行修改操作,编译器对于试图直接修改 const 对象的表达式会产生编译错误。volatile表示“易变的”,即在运行期对象可能在当前程序上下文的控制流以外被修改(例如多线程中被其它线程修改;对象所在的存储器可能被多个硬件设备随机修改等情况):被 volatile 修饰的对象,编译器不会对这个对象的操作进行优化。一个对象可以同时被 const 和 volatile 修饰,表明这个对象体现常量语义,但同时可能被当前对象所在程序上下文意外的情况修改。
(8). 自定义类型(struct、 union、 enum)
-
struct(结构体)
-
基本知识
引用相应的结构成员( . 和 -> )
-
引用某个特定结构中的成员: 结构名.成员
-
假定p是一个指向结构的指针: p->结构成员
-
-
空结构体占用多大内存?
这是C语言的一个灰色地带,没有明确的说明,不同的编译器有不同的实现方式,没有哪个对错,只有哪个更为合理。(下面分别用GCC和G++进行编译的结果)
#include <stdio.h> struct _null { }; int main() { struct _null n1; struct _null n2; printf("%d\n", sizeof(struct _null)); printf("%d, %0X\n", sizeof(n1), &n1); printf("%d, %0X\n", sizeof(n2), &n2); return 0; }
-
由结构体产生柔性数组
-
柔性数组即数组大小待定的数组
-
C语言中结构体的最后一个元素可以是大小未知的数组
-
C语言中可以由结构体产生柔性数组
(数组大小已知在栈上自动分配,未知大小则在动态的空间里面堆上进行申请)
空柔性数组大小:(SoftArray中的array仅是一个待使用的标识符,不占用存储空间)
-
#include <stdio.h> #include <malloc.h> struct SoftArray { int len; int array[]; }; int main() { struct SoftArray* sa = NULL; printf("%d\n", sizeof(sa)); sa = (struct SoftArray*)malloc(sizeof(struct SoftArray) + sizeof(int) * 5); sa->len = 5; printf("%d\n", sizeof(sa)); }
4. 柔性数组的使用——存储斐波拉次数列(斐波拉次数列:前两个元素为1,以后每项为前两项相加之和)
#include <stdio.h> #include <malloc.h> struct SoftArray { int len; int array[]; }; struct SoftArray* create_soft_array(int size) { struct SoftArray* ret = NULL; if( size > 0 ) { ret = (struct SoftArray*)malloc(sizeof(struct SoftArray) + sizeof(int) * size); ret->len = size; } return ret; } void delete_soft_array(struct SoftArray* sa) { free(sa); } void func(struct SoftArray* sa) { int i = 0; if( NULL != sa ) { for(i=0; i<sa->len; i++) { sa->array[i] = i + 1; } } } int main() { int i = 0; struct SoftArray* sa = create_soft_array(10); func(sa); for(i=0; i<sa->len; i++) { printf("%d\n", sa->array[i]); } delete_soft_array(sa); return 0; }
5. 用memcmp()比较结构体可以通过memcmp()来比较2个相同的结构体变量,但这2个变量必须在赋值之前进行清零初始化(否则结果不准确),或者2者是通过直接对等赋值而来。另外,结构体的命名对memcmp()没有影响(只要内部结构一致)
-
-
union(联合声明)
-
union和struct的区别
-
struct中的每个域在内存中都独立分配空间
-
union只分配最大域的空间,所有域共享这个空间(任意时刻它最多只能存储其中的一个成员)
#include <stdio.h> struct A { int a; int b; int c; }; union B { int a; int b; int c; }; int main() { printf("sizeof(struct A) = %d\n",sizeof(struct A)); printf("sizeof(union B) = %d\n",sizeof(union B)); return 0; }
-
-
union和struct联合用法
通常情况,我们无法检查联合的某一个成员,除非已用该成员给联合赋值。但是,有一个特殊情况可以简化联合的使用:如果一个联合包含共享一个公共初始序列的多个结构,并且该联合当前包含这些结构中的某一个,则允许引用这些结构中任意结构的公共初始化部分。
union{ struct { int type; } n; struct { int type; int intnode; } ni; struct { int type; float floatnode; } nf; } u; ... u.nf.type = FLOAT; u.nf.floatnode = 3.14; ... if(u.n.type == FLOAT) ...sin(u.nf.floatnode)...
-
union使用的注意事项
union的使用受系统大小端的影响
#include <stdio.h> int system_mode() { union SM { int i; char c; }; union SM sm; sm.i = 1; return sm.c; } int main() { printf("System Mode: %d\n", system_mode()); return 0; }
-
-
enum(枚举类型)
-
基本知识
-
enum是一种自定义类型,定义的值是C语言中真正意义上的常量。
-
enum默认常量在前一个值的基础上依次加1,默认第一个常量为0
-
enum类型的变量只能取定义时的离散值
#include <stdio.h> enum color { GREEN, RED = 2, BULE }; int main() { printf("GREEN = %d\n", GREEN); printf("RED = %d\n", RED); printf("BULE = %d\n", BULE); return 0; }
-
-
枚举类型和#define的区别
-
#define宏常量只是简单的进行值替换,枚举常量是真正意义上的常量
-
#define宏常量无法被调试,枚举常量可以
-
#define宏常量无类型信息,枚举常量是一种特定类型的常量
-
-
(9). typedef
-
基本概念
-
typedef用于给一个已经存在的数据类型重命名
-
typedef并没有产生新的类型
-
重定义的类型
-
可以在typedef语句之后定义
-
不能进行unsigned和signed扩展
#include <stdio.h> typedef int Int32; struct _tag_point { int x; int y; }; typedef struct _tag_point Point; typedef struct { int length; int array[]; } SoftArray; typedef struct _tag_list_node ListNode; struct _tag_list_node { ListNode* next; }; int main() { Int32 i = -100; // int unsigned Int32 ii = 0; //error Point p; // struct _tag_point SoftArray* sa = NULL; ListNode* node = NULL; // struct _tag_list_node* return 0; }
-
-
-
typedef和#define的区别
-
typedef是给已有类型取别名
-
#define为简单的字符串替换,无别名的概念
// demo1 #include <stdio.h> typedef char* PCHAR; int main() { PCHAR p1, p2; char c; p1 = &c; p2 = &c; return 0; }
// demo2 #include <stdio.h> #define PCHAR char* int main() { PCHAR p1, p2; char c; p1 = &c; p2 = &c; return 0; }
由上面对比可知,demo1 :typedef为重命名,故 p1, p2 都为 char*;demo2:#define 为简单的字符串替换,PCHAR p1, p2;替换完为 char* p1, p2;(其中 p1 为 char 型指针,p2 为 char 型变量),故才出现错误。
-