goto(跳转,禁用)
高手潜规则:禁用goto
项目经验:程序质量与goto的出现次数成反比
最后的判决:将goto打入冷宫
#include
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
int main()
{
printf("%d\n", sizeof(void));
return 0;
}
小贴士:
上述代码在 ASNI C 编译器中是无法通过编译,但在支持 GNU 标准的 gcc 编译器而言是合法的
void指针的意义:
C语言规定只有相同类型的指针才可以相互赋值
void*指针作为左值用于“接收”任意类型的指针
void*指针作为右值赋值给其它指针时需要强制类型转换
#include
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
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
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;
}
const(定义的不是真的常量)
“const”含义是“请做为常量使用”,而并非“放心吧,那肯定是个常量”。在现在C语言编译器中,将const修饰的全局生命周期的变量(全局变量、static修饰的变量)存储于只读存储区中,修改将会导致程序崩溃,而标准C语言编译器则存储于可修改的全局数据区,其值依然可以改变。
const 修饰变量
在C语言中const修饰的变量是只读的(不能作为左值),其本质还是变量
本质上const只对编译器有用,在运行时无用
const修饰的局部变量在栈上分配空间,修饰的全局变量在全局数据区分配空间,故可以通过指针改变值
#include
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
int main()
{
int i = 0, t = 0;
const int* p = &i;
int const* y = &t;
*p = 3;
*y = 3;
return 0;
}
左数右指:
#include
int main()
{
int i = 0;
const int* const p = &i;
*p = 3;
p = NULL;
return 0;
}
const修饰函数参数和返回值
const修饰函数参数表示在函数体内不希望改变参数的值
const修饰函数返回值表示返回值不可改变,多用于返回指针的情形
小贴士:
C语言中字符串字面量存储于只读存储区中,在程序中需要使用 const char* 指针。
#include
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 修饰,表明这个对象体现常量语义,但同时可能被当前对象所在程序上下文意外的情况修改。
struct(结构体)
基本知识
引用相应的结构成员( . 和 -> )
引用某个特定结构中的成员: 结构名.成员
假定p是一个指向结构的指针: p->结构成员
空结构体占用多大内存?
这是C语言的一个灰色地带,没有明确的说明,不同的编译器有不同的实现方式,没有哪个对错,只有哪个更为合理。(下面分别用GCC和G++进行编译的结果)
#include
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
#include
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));
}
(斐波拉次数列:前两个元素为1,以后每项为前两项相加之和)
#include
#include
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;
}
可以通过memcmp()来比较2个相同的结构体变量,但这2个变量必须在赋值之前进行清零初始化(否则结果不准确),或者2者是通过直接对等赋值而来。另外,结构体的命名对memcmp()没有影响(只要内部结构一致)
union(联合声明)
union和struct的区别
struct中的每个域在内存中都独立分配空间
union只分配最大域的空间,所有域共享这个空间(任意时刻它最多只能存储其中的一个成员)
#include
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)...
enum(枚举类型)
基本知识
enum是一种自定义类型,定义的值是C语言中真正意义上的常量。
enum默认常量在前一个值的基础上依次加1,默认第一个常量为0
enum类型的变量只能取定义时的离散值
#include
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宏常量无类型信息,枚举常量是一种特定类型的常量
基本概念
typedef用于给一个已经存在的数据类型重命名
typedef并没有产生新的类型
重定义的类型
可以在typedef语句之后定义
不能进行unsigned和signed扩展
#include
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
typedef char* PCHAR;
int main()
{
PCHAR p1, p2;
char c;
p1 = &c;
p2 = &c;
return 0;
}
// demo2
#include
#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 型变量),故才出现错误。