C课程学习大纲
第 1 课 基本数据类型 2
第 2 课 有符号与无符号 2
第 3 课 浮点数的秘密 4
第 4 课 类型转换 5
第 5 课 变量的属性 6
第 6 课 分支语句 8
第 7 课 循环语句 10
第 8 课 goto 和 void 分析 11
第 9 课 const 和 volatile 分析 13
第 10 课 struct 和 union 分析 15
第 11 课 enum,sizeof,typedef 分析 17
第 12 课 注释符号 18
第 13 课 接续符合转义符 20
第 14 课 单引号和双引号 22
第 15 课 逻辑运算符分析 23
第 16 课 位运算符分析 24
第 17 课 ++和—操作符分析 25
第 18 课 三目运算符和逗号表达式 26
第 19 课 编译过程简介 28
第 20 课 链接过程简介 28
第 21 课 宏定义与使用分析 29
第 22 课 条件编译使用分析 31
第 23 课 #error 和#line 使用分析 32
第 24 课 #pragma 使用分析 33
第 25 课 #和##操作符使用分析 36
第 26 课 指针的本质分析 36
第 27 课 数组的本质分析 38
第 28 课 指针和数组分析(上) 39
第 29 课 指针和数组分析(下) 40
第 30 课 c 语言中的字符串 41
第 31 课 字符串典型问题分析 42
第 32 课 数组指针和指针数组分析 44
第 33 课 main 函数与命令行参数 45
第 34 课 多维数组和多维指针 47
第 35 课 数组参数和指针参数分析 48
第 36 课 函数与指针分析 49
第 37 课 指针阅读技巧分析 50
第 38 课 动态内存分配 50
第 39 课 程序中的三国天下 51
第 40 课 程序的内存布局 53
第 41 课 内存操作经典问题分析一 55
第 42 课 内存操作经典问题分析二 55
第 43 课 函数的意义 57
第 44 课 函数参数的秘密(上) 58
第 45 课 函数参数的秘密(下) 59
第 46 课 函数与宏分析 60
第 47 课 递归函数分析 61
第 48 课 函数设计原则(完结) 62
/******************************************************************/
第 1 课 基本数据类型
什么是数据类型?
数据类型可以理解为固定内存大小的别名
数据类型是创建变量的模子
char 1byte
short 2byte
int 4byte
变量时一段实际连续存储空间的别名
程序中通过变量来申请并命名存储空间
通过变量的名字可以使用存储空间
例1、数据类型
#include <stdio.h>
int main(void)
{
char c=0;
short s=0;
int i=0;
printf("%d,%d\n",sizeof(char),sizeof(c)); //1 1
printf("%d,%d\n",sizeof(short),sizeof(s)); //2 2
printf("%d,%d\n",sizeof(int),sizeof(i)); //4 4
}
例2、数据类型
#include <stdio.h>
typedef int int32;
typedef unsigned char byte;
typedef struct _tag_ts
{
byte b1;
byte b2;
short s;
int32 i;
}ts;
int main(void)
{
int32 i32;
byte b;
ts t;
printf("%d,%d\n",sizeof(int32),sizeof(i32)); //4 4
printf("%d,%d\n",sizeof(byte),sizeof(b)); //1 1
printf("%d,%d\n",sizeof(ts),sizeof(t));// 8 8
}
小结:
数据类型的本质是一个模子
数据类型代表需要占用的内存大小
变量的本质是一段内存的别名
变量隶属于某一种数据类型
变量所在的内存大小取决于其所属的数据类型
第 2 课 有符号与无符号
数据类型的最高位用于标识数据的符号
最高位为 1,表名这个数为负数
最高位为 0,表名这个数为正数
int sign=0;
char i=-5;
short j=5;
int k=-1;
sign =(i&0x80); //sign!=0
sign=(j&0x800); //sign=0;
sign=(k&0x80000000); //sign!=0
例 1 有符号数的符号位
#include <stdio.h>
int main()
{
char c=-5;
short s=6;
int i=-7;
printf("%d\n",((c&0x80)!=0)); //1
printf("%d\n",((s&0x80)!=0)); //0
printf("%d\n",((i&0x80)!=0)); //1
return 0;
}
在计算机内部用补码标识有符号数
正数的补码为正数本身
负数的补码为负数绝对值各位取反后加 1
8位正数 5 的补码为:0000 0101
8位整数-7 的补码为:1111 1001
16位整数 20 的补码为:0000 0000 0001 0100
16位整数-13 的补码为:1111 1111 1111 0011
在计算机内部用原码表示无符号数
无符号数默认为正数
无符号数没有符号位
对于固定长度的无符号数
MAX_VALUE+1->MIN_VALUE
MIN_VALUE-1->MAX_VALUE
signed 和 unsigned
c语言中变量默认为有符号的类型 unsigned 关键字声明变量为无符号类型
#include <stdio.h>
int main()
{
int i; //默认为带符号整型
signed int k; //显示声明变量为带符号整型
unsigned int k; //声明变量为无符号整型
return 0;
}
c 语言中只有整型类型声明 unsigned 变量
例 2、有符号数遇见无符号数
#include <stdio.h>
int main(void)
{
unsigned int i=5;
int j=-10;
if((i+j)>0)
{
printf("i+j>0\n");
}
else
{
printf("i+j<=0\n");
}
return 0;
}
例3 错误的使用了 unsigned
#include <stdio.h>
int main()
{
unsigned int i=0;
for(i=9;i>=0;i--)
{ //死循环
printf("i=%u\n",i);
}
return 0;
}
小结:
有符号数用补码表示
正数的符号位为 0
负数的符号位为 1
无符号数用原码表示
无符号数没有符号位
无符号数只用于表示正数
unsigned 只能修饰整数类型的变量
当无符号数与有符号混合计算时,会将有符号数转换为无符号数后再进行计算,结果为无符号数。
第 3 课 浮点数的秘密
浮点数在内存的存储方式为:符号位,指数,尾数
类型 符号位 指数 尾数
float 1 位(第 31 位) 8 位(第 23-30 位) 23 位(第 0-22 位)
double 1 位(第 63 位) 11 位(第 52-62 位) 52 位(第 0-51 位)
float 与 double 类型的数据在计算机内部的表示方法是相同的,但由于所占存储空间的不同,其分别能够表示的数值范围和精度不同。
浮点数的转换
1、将浮点数转换成二进制
2、用科学计数法表示二进制浮点数
3、计算指数偏移后的值
注意:计算指数时需要加上偏移量,而偏移量的值与类型有关。
示例:对于指数 6,偏移后的值如下:
float:127+6—>133
double:1023+6—>1029
10进制浮点数的内存表示
实数 8.25 的内存中的 float 表示
8.25 的二进制表示:1000.01->1.00001*(2^3)
符号位:0
指数:127+3->130->1000 0010
小数:0000 1 内存中 8.25 的 float 表示:
01000 0010 0000 1 0000 0000 0000 0000 00->0x4104 0000
例 1 10 进制浮点数内存表示
#include <stdio.h>
int main(void)
{
float f=8.25;
unsigned int* p=(unsigned int *)&f;
printf("0x%08x\n",*p);
return 0;
}
int 类型的范围:[-231,231-1]
float 类型的范围:[-3.4*1038,3.4*1038]
思考:
int 和 float 都占用 4 字节的内存,为什么?
float 却比 int 的范围大的多呢?
浮点类型的秘密
float 能表示具体数字的个数与 int 相同
float 可表示的数字之间不是连续的,存在间隙 float 只是一种近似的表示法,不能作为精确数使用
由于内存表示法相对复杂,float 的运算速度比 int 慢得多
注意:double 与 float 具有相同的内存表示法,因此 double 也是不精确的。由于 double 占用的内存较多,所以能表示的精度比 float 高。
例2 float 类型的不精确
#include <stdio.h>
int main(void)
{
float f=3.1415f;
float f1=123456789;
printf("%0.10f\n",f);
printf("%0.10f\n",f1);
return 0;
}
小结:
浮点类型与整数类型的内存表示法不同
浮点类型的内存表示更复杂
浮点类型可表示的范围更大
浮点类型是一种不精确的类型
浮点类型的运算速度较慢
第 4 课 类型转换
c语言中的数据类型可以进行转换强制类型转换
隐式类型转换
int main()
{
long l=800;
int i=(int)l; //强制类型转换
return 0;
}
int main()
{
short s=800;
int i=s; //隐式类型转换,no error,no warning
return 0;
}
强制类型转换的语法
(Type)var_name;
(Type)value;
强制类型转换的结果
目标类型能够容纳目标值:结果不变
目标类型不能容纳目标值:结果将产生截断
注意:不是所有的强制类型转换都能成功,当不能进行强制类型转换时,编译器将产生错误信息。
例 1 强制类型转换分析
#include <stdio.h>
struct ts
{
int i;
int j;
};
struct ts t;
int main(void)
{
short s=0x1122;
char c=(char)s; //0x22
int i=(int)s; //0x22
int j=(int)3.1415; //3
unsigned long long p=(unsigned long)&t;
//long l=(long)t; //error
//t=(struct t)l; //error
printf("s=%x\n",s);
printf("c=%x\n",c);
printf("i=%x\n",i);
printf("j=%x\n",j);
printf("p=%x\n",p);
printf("&t=%p\n",&t);
return 0;
}
隐式类型转换
编译器主动进行的类型转换
char c=0; //变量 c 占用 1 个字节
short s=c; //从 c 到 s 隐私类型转换
int i=s; //从 s 到 i 隐私类型转换
long l=i; //从 i 到 l 隐私类型转换
注意:低类型到高类型的隐式类型转换时安全的,不会产生截断;高类型到低类型的隐式类型转换是不安全的,导致不正确的结果。
表达式的隐式类型转换
隐式类型转换的发生点
算术运算式中,低类型转换为高类型
赋值表达式中,表达式的值转换为左边变量的类型函数调用时,实参转换为形参的类型
函数返回值,return 表达式转换为返回值类型
例 2 隐式类型转换分析
#include <stdio.h>
int main(void)
{
char c='a'; //safe
int i=c;
unsigned int j=0x11223344;
short s=j; //unsafe
printf("c=%c\n",c);
printf("i=%d\n",i);
printf("j=%x\n",j);
printf("s=%x\n",s);
printf("sizeof(c+s)=%d\n",sizeof(c+s));
return 0;
}
小结:
强制类型转换由程序员负责完成
转换可能发生截断
转换不区分类型的高低
转换不成功时,编译器给出错误信息
隐式类型转换由编译器自动完成
低类型向高类型的转换时安全的
高类型向地类型的转换时不安全的
标准 c 编译器的类型检查是比较宽松的,因此隐式类型转换可能带来意外的错误。
第 5 课 变量的属性
c语言的变量可以有属性
在定义变量的时候可以加上“属性”关键字
“属性”关键字指明变量的特有意义
语法:
property type var_name;
示例:
int main()
{
auto char i;
register int i;
static int i;
extern double m;
return 0;
}
auto 关键字
auto 即 c 语言中局部变量的默认属性
auto 表名将被修饰的变量存储于栈上
编译器默认所有的局部变量都是 auto 的
示例:
void f()
{
int i; //局部变量默认为 auto
auto int j; //显示声明 auto 属性
}
register 关键字
register 关键字指明将局部变量存储于寄存器中
register 只是请求寄存器变量,但不一定请求成功
register 变量的必须是 cpu 寄存器可以接受的值
不能用&运算获取 register 变量的地址
#include <stdio.h>
register int g_v; //error
int main()
{
resiter char var;
printf(“0x%08x”,&var); //error
return 0;
}
static 关键字
static 关键字指明变量的“静态”属性
static 修饰的局部变量存储在程序静态区
static 关键字同时具有“作用域限定符”的意义 static 修饰的全局变量作用域只是声明的文件中 static 修饰的函数作用域只是声明的文件中
#include <stdio.h>
int g_v; //全局变量,程序的任意地方均能访问
static int g_va; //静态全局变量,只有当前文件中可访问
int main()
{
int var; //局部变量,在栈上分配空间
static int svar; //静态局部变量,在静态数据区分配空间
return 0;
}
例1 auto,register,static 对比
#include <stdio.h>
int f1()
{
int r=0;
r++;
return r;
}
int f2()
{
static int r=0;
r++;
return r;
}
int main()
{
auto int i=0; //显示声明auto属性,i为站变量
static int k=0; //局部变量k到存储区位于静态区,作用域位于main中
register int j=0; //向编译器申请将j存储与寄存器中
printf("%p\n",&i);
printf("%p\n",&k);
//printf("%p\n",&j); //error
for(i=0;i<5;i++)
{
printf("%d\n",f1());
}
for(i=0;i<5;i++)
{
printf("%d\n",f2());
}
return 0;
}
extern 关键字
extern 用于声明“外部”定义的变量和函数
extern 变量在文件的其他地方分配空间
extern 函数在文件的其他地方定义
extern 用于“告诉”编译器用 C 方式编译
C++编译器和一些变种 C 编译器默认会按“自己”的方式编译函数和变量,通过 extern 关键
字可以命令编译器“以标准 C 方式进行编译”。
extern “C”
{
int f(int a,int b)
{
return a+b;
}
}
例2 static 和 extern 的使用
#include <stdio.h>
extern int getI();
int main(void)
{
printf("%d\n",getI());
return 0;
}
//int g_i;
static int g_i=0;
int getI()
{
return g_i;
}
小结:
auto 变量存储在程序的栈中,默认属性
static 变量存储在程序静态区中
register 变量请求存储于 cpu 寄存器中
extern 变量在文件的其他地方分配空间
extern 能够指示编译器按照标准 c 方式编译程序
第 6 课 分支语句
if 语句分析
if 语句用于根据条件选择执行语句
else 不能独立存在且总是与它最近的 if 相匹配
else 语句后可以接连其他 if 语句
if(condition)
{
//statement 1
}
else
{
//statement w
}
if 语句分析
if 语句中零值比较的注意点
bool 性变量应该直接出现于条件中,不要进行比较
变量和 0 值比较时,0 值应该出现在比较符号左边
float 型变量不能直接进行 0 值比较,需要定义精度
bool b=TRUE;
if(b)
{
//statement 1
}
else
{
//statement 2
}
int i=1;
if(0==i)
{
//statement 1
}
else
{
//statement 2
}
define EPSINON 0.0000001
float f=0.0;
if((-EPSINON<=f)&&(f<=EPSINON))
{
//statement 1
}
else
{
//statement
}
switch 语句分析
switch 语句对应单个条件多个分值的情形
case 语句分支必须要有 break,否则会导致分支重叠
default 语句有必要加上,以处理特殊情况
switch(expression)
{
case CONST_1;
//code block
break;
case CONST_2;
//code block
break;
default:
//code block
}
switch 语句分析
case 语句中的值只能是整型或字符型
case 语句的排列顺序
按字母或数字顺序各条语句
正常情况放在前面,异常情况放在后面
default 语句只用于处理真正的默认情况
例1 if 和 switch 使用示例
#include <stdio.h>
void f1(int i)
{
if(i<6)
{
printf("failed!\n");
}
else if((6<=i)&&(i<=8))
{
printf("Good!\n");
}
else
{
printf("perfect!\n");
}
}
void f2(char i)
{
switch(i)
{
case 'c':
printf("compile\n");
break;
case 'd':
printf("debug\n");
break;
case 'o':
printf("object\n");
break;
case 'r':
printf("run\n");
break;
//case 0.1:
// printf("0.1\n");
// break;
default:
printf("unknow\n");
break;
}
}
int main(void)
{
f1(5);
f1(9);
f1(7);
f2('o');
f2('d');
f2('e');
//f2(0.1);
return 0;
}
例2 有趣的角色对换
#include <stdio.h>
void f1(int i)
{
switch(i<b)
{
case 1:
printf("falied\n");
break;
default:
switch((6<=i)&&(i<=8))
{
case 1:
printf("good\n")
break;
default:
printf("perfect\n");
break;
}
break;
}
}
void f2(char i)
{
if('c'==i)
{
printf("compile\n");
}
else if('d'==i)
{
printf("debug\n");
}
else if('o'==i)
{
printf("run\n");
}
else
{
printf("unknown\n");
}
}
int main(void)
{
f1(5);
f1(9);
f1(7);
f2('o');
f2('d');
f2('e');
//f2(0.1);
return 0;
}
小结:
if 语句适用于复杂逻辑进行判断的情形中
switch 语句适用于对离散值进行判断的情形中
if 语句和 switch 语句在功能上可以相互替换
if 语句对于“按片”分支判断的情形更加简洁
switch 语句对于多分支判断的情形更加简洁
第 7 课 循环语句
循环语句分析
循环语句的基本工作方式
通过条件表达式判定是否执行循环体
条件表达式遵循 if 语句表达式的原则
do,while,for 的区别
do 语句先执行后判断,循环体至少执行一次
while 语句先判断后执行,循环体可能不执行
for 语句先判断后执行,相比 while 更简洁
do。。。while 语句循环方式
do
{
//loop
}
while(condition);
while 语句循环方式
while(condition)
{
//loop
}
for 语句的循环方式
for(i=0;condition;i++)
{
//loop
}
例 1 三种循环语句使用对比
#include <stdio.h>
int f1(int n)
{
int ret=0;
if(n>0)
{
do
{
ret+=n;
n--;
}
while(n>0);
}
return ret;
}
int f2(int n)
{
int ret=0;
while(n>0)
{
ret+=n;
n--;
}
return ret;
}
int f3(int n)
{
int ret=0;
int i=0;
for(i=0;i<=n;i++)
{
ret+=i;
}
return ret;
}
int main(void)
{
printf("%d\n",f1(100));
printf("%d\n",f2(100));
printf("%d\n",f3(100));
return 0;
}
循环语句分析
break 和 continue 的区别
break 表示终止循环的执行
continue 表示终止本次循环,进入下次循环执行
思考:
switch 能否用 continue 关键字?为什么?
例2 continue 和 break 的区别
#include <stdio.h>
void f1(int n)
{
int i=0;
for(i=1;i<=n;i++)
{
if((i%2)==0)
{
break;
}
printf("%d ",i);
}
printf("\n");
}
void f2(int n)
{
int i=0;
for(i=1;i<=n;i++)
{
if((i%2)==0)
{
continue;
}
printf("%d ",i);
}
printf("\n");
}
int main()
{
f1(10);
f2(10);
return 0;
}
例3 do 和 break 的妙用
#include <stdio.h>
#include <malloc.h>
int fun(int n)
{
int i=0;
int ret=0;
int *p=(int *)malloc(sizeof(int)*n);
do
{
if(NULL==p) break;
if(n<5) break;
if(n>100) break;
for(i=0;i<n;i++)
{
p[i]=i;
printf("%d ",p[i]);
}
ret=1;
}while(0);
printf("free(p)\n");
free(p);
return ret;
}
int main(void)
{
if(fun(10))
{
printf("ok\n");
}
else
{
printf("error\n");
}
return 0;
}
小结:
for 循环先进行判断再进入循环体
for 循环适合于循环次数固定的场合
while 循环先进行判断再进入循环体执行
while 循环适合于循环次数不固定的场合
do。。。while 循环先执行循环体再进行条件判断
do。。。while 循环至少执行一次循环体
第8 课 goto 和 void 分析
高手潜规则:禁用 goto
项目经验:程序质量与 goto 的出现次数成反比
最后的判决:将 goto 打入冷宫
例1 goto 副作用复习
#include <stdio.h>
#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);
}
int main(void)
{
printf("begin...\n");
printf("func(1)\n");
func(1);
printf("func(-1)\n");
func(-1);
printf("end\n");
return 0;
}
void 的意义
void 修饰函数返回值和参数
如果函数没有返回值,那么应该讲其声明为 void
如果函数没有参数,应该声明其参数为 void
void 修饰函数返回值和参数是为了表示“无”
include
#include <stdio.h>
f()
{
}
int main()
{
int i=f(1,2,3,4);
printf("%d\n",i);
return 0;
}
void 的意义
不存在 void 变量
c 语言没有定义 void 究竟多大内存的别名
没有 void 的标尺,无法在内存中裁剪出 void 对应的变量
include
#include <stdio.h>
int main()
{
void var; //error
void array[5]; //error
void *pv;
return 0;
}
小贴士
ANSI C:标准 c 语言的规范
扩展 C:在 ANSI C 的基础上进行了扩展
include
#include <stdio.h>
int main()
{
printf("%d\n",sizeof(void));
return 0;
}
上面的代码 ANSI C 编译器中无法通过编译,但是对于支持 GNU 标准的 gcc 编译器而言时候合法的。
void 指针的意义
c语言中规定只有相同类型的指针才可以相互赋值
void*指针作为左值作用于“接收”任意类型的指针void*指针作为右值使用时需要强制类型转换
int *pi=(int *)malloc(sizeof(int));
char *pc=(char *)malloc(sizeof(char));
void *p=NULL;
int *pni=NULL;
char *pnc=NULL;
p=pi; //ok
pni=p; //oops
p=pc; //ok
pnc=p; //oops
例5 通过 void*实现 MemSet 函数
#include <stdio.h>
void MemSet(void *src,int length,unsigned char n)
{
unsigned char *p=(unsigned char *)src;
int i=0;
for(i=0;i<length;i++)
{
p[i]=n;
}
}
int main()
{
int a[5];
int i=0;
MemSet(a,sizeof(a),0);
for(i=0;i<5;i++)
{
printf("%d\n",a[i]);
}
return 0;
}
小结:
现代软件工程中禁用 goto 语句
void 是一种抽象的数据类型
void 类型不能用于定义变量
void 类型用于声明函数无参数
void 类型用于声明函数无返回值
可以定义 void*类型的指针
void*类型的指针可以接受任意类型的指针值
第9 课 const 和 volatile 分析
const 只读变量
const 修饰的变量时只读的,本质还是变量
const 修饰的局部变量在栈上分配空间
const 修饰的全局变量在全局数据区分配空间
const 只在编译期间有用,在运行期无用
const 修饰的变量不是真的常量,它只是告诉编译器该变量不能出现在赋值符号的左边。
const 全局变量的分歧
在现代 c 语言编译器中,修改 const 全局变量将导致程序崩溃
注意:标准 c 语言编译器不会讲 const 修饰的全局变量存储于只读存储区中,而是存储于可修改的全局数据区,其值依然可以改变。
例1 const 的变量本质
#include <stdio.h>
int main(void)
{
const int cc=1;
printf("cc=%d\n",cc);
cc=3;
printf("cc=%d\n",cc);
return 0;
}
例2 const 的变量本质 1
#include <stdio.h>
int main(void)
{
const int cc=1;
int *p=(int *)&cc;
printf("cc=%d\n",cc);
*p=3;
printf("cc=%d\n",cc);
return 0;
}
例3 const 的变量本质 2
#include <stdio.h>
const int g_cc=2;
int main(void)
{
const int cc=1;
int *p=(int *)&cc;
printf("cc=%d\n",cc);
*p=3;
printf("cc=%d\n",cc);
p=(int *)&g_cc;
printf("g_cc=%\n",g_cc);
*p=4;
printf("g_cc=%d\n",g_cc);
return 0;
}
const 的本质
c 语言中的 const 使得变量具有只读属性
现代 c 编译器中的 const 将具有全局生命周期的变量存储于只读存储区
const 不能定义真正意义上的常量
例4 const 的本质分析 3
#include <stdio.h>
const int g_array[5]={0};
void modify(int *p,int v)
{
*p=v;
}
int main()
{
int const i=0;
const static int j=0;
int const array[5]={0};
modify((int *)&i, 1); //ok
//modify((int *)&j, 2); //error
modify((int *)&array[0], 3); //ok
//modify((int *)&g_array[0], 4); //error
printf("i=%d\n",i);
printf("j=%d\n",j);
printf("array[]0=%d\n",array[0]);
printf("g_array[0]=%d\n",g_array[0]);
}
const 修饰函数参数和返回值
const 修饰函数参数表示在函数体内不希望改变参数的值
const 修饰函数返回值表示返回值不可改变,多用于返回指针的情形
小贴士:c 语言中的字符串字面量存储于只读存储区中,在程序中需要使用 const char*指针
include
#include <stdio.h>
const char *f(const int i)
{
//i=5;
return "baby";
}
int main(void)
{
const char *pc=f(0);
printf("%d\n",pc);
pc[6]='_';
printf("%s\n",pc);
return 0;
}
volatile 可理解为“编译器警告指示字”
volatile 告诉编译器必须每次去内存中取变量值
volatile 主要修饰可能被多个线程访问的变量
volatile 也可以修饰可能被未知因数更改的变量
int obj=10;
int a=0;
int b=0;
a=obj;
sleep(100);
b=obj;
编译器在编译的时候发现 obj 没有被当成左值使用,因此会“聪明”的直接将 obj 替换成 10,而把 a 和 b 读赋值为 10.
const volatile int i=0;
变量 i 具有什么样的特性?
编译器如何处理这个变量?
小结:
const 是的变量具有只读属性
const 不能定义真正意义上的常量
const 将具有全局生命期的变量存储于只读存储区
volatile 强制编译器减少优化,必须每次从内存中取值
第10 课 struct 和 union 分析
struct 的小秘密
c 语言中的 struct 可以看作变量的集合
struct 的问题:
空结构体占用多大内存?
struct TS
{
};
sizeof(struct TS)=?
例 1 空间结构体的大小
#include <stdio.h>
struct TS
{
};
int main(void)
{
struct TS t1;
struct TS t2;
printf("sizeof(struct TS)=%d\n",sizeof(struct TS));
printf("sizeof(t1)=%d,&t1=%p\n",sizeof(t1),&t1);
printf("sizeof(t2)=%d,&t2=%p\n",sizeof(t2),&t2);
}
结构体与柔性数组
柔性数组即数组大小待定的数组
c语言中可以由结构体产生柔性数组
c语言中结构体的最后一个元素可以是大小未知的数组 struct SoftAray
{
int len; int array[];
};
sizeof(struct SoftArray)=?
SoftArray 中的 array 仅是一个待使用的标识符,不占用存储空间
例 2 柔性数组
#include <stdio.h>
struct SoftArray
{
int len;
int array[];
};
int main(void)
{
printf("sizeof(struct SoftArray)=%d\n",sizeof(struct SoftArray));
return 0;
}
柔性数组的用法
struct SoftArray
{
int len;
int array[];
};
//….
struct SoftArray *sa=NULL;
sa=(struct SoftArray*)malloc(sizeof(struct SoftArray)+sizeof(int)*5); sa->len=5;
例 3 柔性数组使用分析
#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;
}
}
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;
}
c 语言中的 union
c 语言中的 union 在语法上与 struct 相似
union 只分配最大成员的空间,所有成员共享这个空间
struct A
{
int a;
int b;
int c;
};
union B
{
int a;
int b;
int c;
};
int main(void)
{
printf(“%d\n”,sizeof(struct A)); //12
printf(“%d\n”,sizeof(union B)); //4
}
union 的使用受系统大小端的影响
int i=1
0x01 0x00 0x00 0x00
———————————-
小端模式
int i=1
0x00 0x00 0x00 0x01
———————————-
大端模式
union C
{
int i;
char c;
};
union C c;
c.i=1;
printf(“%d\n”,c.c); //??
例 4 编程判断系统的大小端
#include <stdio.h>
int system_mode()
{
union SM
{
int i;
char c;
};
union SM sm;
sm.i=1;
return sm.c;
}
int main(void)
{
printf("system mode:%d\n",system_mode());
return 0;
}
小结:
struct 中的每个数据成员有独立的存储空间
struct 可以通过最后的数组标识符产生柔性数组
union 中的所有数据成员共享同一个存储空间
union 的使用会受到系统大小端的影响
第11 课 enum,sizeof,typedef 分析
枚举类型的使用方法
enum 是 c 语言中的一种自定义类型
enum 值是可以根据需要自定义的整型值
第一个定义的 enum 值默认为 0
默认情况下的 enum 值是在前一个定义值的基础上加 1
enum 类型的变量只能取定义时的离散值
enum clolor
{
GREEN,
RED=2,
BLUE
};
enum color c=GREEN;
printf(“%d\n”,c);
枚举类型的特殊意义
enum 中定义的值是 c 语言中真正意义上的常量
在工程中 enum 多用于定义整型常量
enum //无名枚举,用于定义常量
{
ARRAY_SIZE=10, //定义数组大小
};
int array[ARRAY_SIZE]={0};
int i=0;
for(i=0;i
#include <stdio.h>
enum
{
ARRAY_SIZE=10
};
enum Color
{
RED=0x00FF0000,
GREEN=0x0000FF00,
BLUE=0x000000FF
};
void PrintColor(enum Color c)
{
switch(c)
{
case RED:
printf("Color:RED(0x%08X)\n",c);
break;
case GREEN:
printf("Color:GREEN(0x%08X)\n",c);
break;
case BLUE:
printf("Color:BLUE(0x%08X)\n",c);
break;
}
}
void InitArray(int array[])
{
int i=0;
for(i=0;i<ARRAY_SIZE;i++)
{
array[i]=i+1;
}
}
void PrintArray(int array[])
{
int i=0;
for(i=0;i<ARRAY_SIZE;i++)
{
printf("%d\n",array[i]);
}
}
int main(void)
{
enum Color c=GREEN;
int array[ARRAY_SIZE]={0};
PrintColor(c);
InitArray(array);
PrintArray(array);
return 0;
}
sizeof 关键字的用法
sizeof 是编译器的内置指示符
sizeof 用于计算类型或变量所占的内存大小
sizeof 的值在编译期就已经确定
sizeof 用于类型:
sizeof(type)
sizeof 用于变量
sizeof(var)或 sizeof var
int var=0;
printf(“%d\n”,sizeof(int));
printf(“%d\n”,sizeof(var));
printf(“%d\n”,sizeof var);
为sizeof 关键字正名
sizeof 是 c 语言的内置关键字而不是函数
在编译过程中所有的 sizeof 将被具体的数值所替换程序的执行过程中与 sizeof 没有任何关系
下面的程序输出什么?
int var=0;
int size=sizeof(var++)
printf(“var=%d,size=%d\n”,var,size);
例2 sizeof 的本质
#include <stdio.h>
int f()
{
printf("baby\n");
return 0;
}
int main(void)
{
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;
}
typedef 的意义
typedef 用于给一个已经存在的数据类型重命名
typedef 本质上不能产生新的类型
typedef 重命名的类型:
可以在 typedef 语句之后定义
不能被 unsigned 和 signed 修饰
用法:
typedef type new_name;
例3 typedef 使用示例
#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(void)
{
Int32 i=-100;
//unsigned Int32 li=0;
point p;
softarray *sa=NULL;
listnode *node=NULL;
return 0;
}
小结:
enum 用于定义离散值类型
enum 定义的值是真正意义上的常量
sizeof 是编译器的内置指示符
sizeof 不参与程序的执行过程
typedef 用于给类型重命名
重命名的类型可以在 typedef 语句之后定义
第 12 课 注释符号
下面的注释正确吗?
int main()
{
int /*…*/i;
char *s=”abcdefg //ijklmn”;
//is is a\
valid comment?
in /* …*/t j;
return 0;
}
例 1 初探注释规则
#include <stdio.h>
int main()
={
int /*…*/i;
char *s=”abcdefg //ijklmn”;
//is is a\
valid comment?
in /* …*/t j;
return 0;
}
注释规则
编译器在编译过程中使用空格替换整个注释
字符串字面量中的//和/…/不代表注释符号
/…/型注释不能被嵌套
你觉得 y=x/*p 是什么意思?
作者本意:把 x 除以*p 的结果赋值给 y
编译器:将/作为注释的开始,把/后的内容都当成注释内容,直到*/出现为止。
在编译器看来,注释和其他程序元素是平等的。因此作为工程师不能轻视注释。
int main()
{
r=n/2; //r 是 n 的一半
while((r-n/r)<=t) //循环,仅当 r-n/r 不大于 t
{
}
r=r+n*t; //对变量 r 进行赋值
n++; //变量 n 自增 1
return 0;
}
注释用于阐述原因和意图而不是描述程序的运行过程!
例 2 注释
#include<stdio.h>
int main()
{
int y=1;
int x=2;
int *p=&x;
y=x / *p;
return 0;
}
迷惑型的注释
int main()
{
init();
//….
//…
sad=0x723; //R.I.J
//…
//…
finalize();
return 0;
}
写注释不是晒心情,必须无二义性,起到对代码进行提示的作用,避免使用缩写!
忽略型注释
int main()
{
//…
//…
//baby 04/24/2018
/*
我知道这个问题很难解决而且
现在必须依赖这个 contains 函数,
但我以后会用一种更加直观优雅有意义的方式
重写这段代码。
现在这么做只是由于时间紧迫,但我一定会解决。
*/
if(contains(s,”error”))
{
exit(1);
}
//…
//…
return 0;
}
注释是对代码的提示,避免臃肿和喧宾夺主。
例 3 漂亮的程序注释
/*
========================================================================
FILE: Form.c
SERVICES:
GENERAL DESCRIPTION: Concrete implementation of RootForm and base IForm
methods
========================================================================
========================================================================
Copyright ?1999-2005 QUALCOMM Incorporated
All Rights Reserved.
QUALCOMM Proprietary/GTDR
========================================================================
========================================================================
*/
/*==================================================================================
XXXXXXX Confidential Proprietary
(c) Copyright XXXXXXX - All Rights Reserved
Revision History:
Modification
Author Date CR Number Major Changes
---------------------- ------------ ------------ ----------------------------
Daniel Rossler 01/18/2007 LIBkk94550 Add check for NULL pointers
in order to avoid a panic
==================================================================================*/
#include "FormBase.h"
#include "AEESoftkeyWidget.h"
#include "AEEImageWidget.h"
#include "AEEStaticWidget.h"
#include "AEEImageStaticWidget.h"
#include "AEERootContainer.h"
#include "AEEWProperties.h"
#include "AEEVectorModel.h"
#include "AEEWeb.h"
#include "AEERootForm.h"
#include "AEEResFile.h"
#include "FormUtil.h"
#include "AEEDisplayCanvas.h"
#define FORMSTACK_MIN 10
#define FORMSTACK_GROW 2
/
// RootForm
typedef struct RootForm {
Form base;
IRootContainer * piContainer;
AEERect rcContainer;
AEERect rcClient;
IVectorModel * piForms;
ModelListener mlFormActive;
ModelListener mlFormTopmostNonPopup;
IWidget * piTitle;
ImageStaticInfo titleInfo;
IWidget * piSoftkeys;
IWidget * piBackground;
IWidget * piActiveWidget;
IResFile * piThemeFile;
const char * themeFile;
} RootForm;
#define DECL(c) c* me = (c *)po
static __inline IForm *ROOTFORM_TO_IFORM(RootForm *me) {
return (IForm *)me;
}
static __inline Form *ROOTFORM_TO_FORM(RootForm *me) {
return (Form *)me;
}
static __inline IRootForm *ROOTFORM_TO_IROOTFORM(RootForm *me) {
return (IRootForm *)me;
}
static void RootForm_FreeFormEntry(IForm *po)
{
IFORM_Release(po);
}
static void RootForm_UpdateClientArea(RootForm *me)
{
WidgetPos pos;
WExtent titleExtent, skExtent;
if (me->piSoftkeys) {
IWIDGET_GetExtent(me->piSoftkeys, &skExtent);
// Adjust softkey position based on current height
IROOTCONTAINER_GetPos(me->piContainer, me->piSoftkeys, &pos);
pos.y = me->rcContainer.dy - skExtent.height;
IROOTCONTAINER_SetPos(me->piContainer, me->piSoftkeys, WIDGET_ZNORMAL, &pos);
} else {
SETWEXTENT(&skExtent, 0, 0);
}
if (me->piTitle) {
IWIDGET_GetExtent(me->piTitle, &titleExtent);
} else {
SETWEXTENT(&titleExtent, 0, 0);
}
// Calculate client area
SETAEERECT(&me->rcClient, 0, titleExtent.height,
me->rcContainer.dx,
me->rcContainer.dy - skExtent.height - titleExtent.height);
}
static void RootForm_UpdateTheme(RootForm *me, const char *baseName)
{
WExtent wextent;
BUIT_LOG("FORMS EVT: Update Theme Started for %s", baseName);
if (!me->piThemeFile)
return;
if (me->piTitle) {
IWIDGET_SetProperties(me->piTitle, me->piThemeFile, baseName, "Title", "Properties", 0);
IWIDGET_GetPreferredExtent(me->piTitle, &wextent);
wextent.width = me->rcContainer.dx;
IWIDGET_SetExtent(me->piTitle, &wextent);
}
if (me->piSoftkeys) {
IWIDGET_SetProperties(me->piSoftkeys, me->piThemeFile, baseName, "Softkeys", "Properties", 0);
IWIDGET_GetPreferredExtent(me->piSoftkeys, &wextent);
wextent.width = me->rcContainer.dx;
IWIDGET_SetExtent(me->piSoftkeys, &wextent);
}
if (me->piBackground) {
IWIDGET_SetProperties(me->piBackground, me->piThemeFile, baseName, "Background", "Properties", 0);
}
// Update client area since sizes may have changed
RootForm_UpdateClientArea(me);
BUIT_LOG("FORMS EVT: Update Theme Finished for %s", baseName);
}
// updates the rootform with the background image, softkey and
// title text of the TOS form.
static void RootForm_Update(RootForm *me, uint32 dwItemMask, IForm* piForm)
{
boolean bPopup = 0;
// get form's popup flag
bPopup = IFORM_GetIsPopup(piForm);
// if the form's widget has changed, update the scroll model
// for the scroll indicator in the softkey widget
if (dwItemMask & FORMITEM_WIDGET) {
IWidget *piWidget = NULL;
// get form's widget
IFORM_GetWidget(piForm, WID_FORM, &piWidget);
// update the widget and the scroll model
if (piWidget) {
// if the active widget has been changed underneath us...
if (me->piActiveWidget && piWidget != me->piActiveWidget) {
// this block will only be executed when the form widget is changed
// by the application logic while the form is active
WidgetPos pos;
WExtent we;
IWIDGET_MoveFocus(FORM_WIDGET(me), (IWidget*)WIDGET_FOCUS_NONE);
IWIDGET_GetExtent(me->piActiveWidget, &we);
IWIDGET_SetExtent(piWidget, &we);
// remove the previously active widget from the root container
if (AEE_SUCCESS == IROOTCONTAINER_GetPos(me->piContainer, me->piActiveWidget, &pos)) {
IROOTCONTAINER_Remove(me->piContainer, me->piActiveWidget);
}
// add the new widget to the root container
IROOTCONTAINER_Insert(me->piContainer, piWidget, WIDGET_ZTOPMOST, &pos);
// and remember it fondly
RELEASEIF(me->piActiveWidget);
me->piActiveWidget = piWidget;
ADDREFIF(piWidget);
// set focus to the new widget
IWIDGET_MoveFocus(FORM_WIDGET(me), piWidget);
} else if (!me->piActiveWidget) {
me->piActiveWidget = piWidget;
ADDREFIF(piWidget);
}
}
RELEASEIF(piWidget);
}
// if the form's background image has changed...
// if form is a popup, then retain the background image
// from the previous form
if (dwItemMask & FORMITEM_BACKGROUND && me->piBackground && !bPopup) {
IImage *pii = NULL;
// Try to grab the image from the new form.
IFORM_GetBGImage(piForm, &pii);
// If non-existent, try defaulting to the root form
if (!pii) IFORM_GetBGImage(ROOTFORM_TO_IFORM(me), &pii);
// Apply the result (NULL or otherwise) to our background widget
IWIDGET_SetImage(me->piBackground, pii);
RELEASEIF(pii);
}
// if the form's title text has changed... retain previous title
// if we are a popup
if ((dwItemMask & FORMITEM_TITLE) && me->piTitle && !bPopup) {
// Release image. Text is owned by form
RELEASEIF(me->titleInfo.piImage);
IFORM_GetTextPtr(piForm, FID_TITLE, &me->titleInfo.pwText);
IFORM_GetTitleImage(piForm, &me->titleInfo.piImage);
// Set title info
IWIDGET_SetImageStaticInfo(me->piTitle, &me->titleInfo, 0);
}
// if the form's softkey text has changed...
if ((dwItemMask & FORMITEM_SOFTKEY) && me->piSoftkeys) {
IForm* piTopForm = IROOTFORM_GetTopForm(ROOTFORM_TO_IROOTFORM(me));
AECHAR *pwsz = NULL;
IWidget *piKey = NULL;
if (piTopForm == piForm) {
// set softkey 1 text
IFORM_GetTextPtr(piForm, FID_SOFTKEY1, &pwsz);
if (AEE_SUCCESS == IWIDGET_GetSoftkey(me->piSoftkeys, PROP_SOFTKEY1, &piKey)) {
IWIDGET_SetText(piKey, pwsz, 0);
}
RELEASEIF(piKey);
// set softkey 2 text
IFORM_GetTextPtr(piForm, FID_SOFTKEY2, &pwsz);
if (AEE_SUCCESS == IWIDGET_GetSoftkey(me->piSoftkeys, PROP_SOFTKEY2, &piKey)) {
IWIDGET_SetText(piKey, pwsz, 0);
}
}
RELEASEIF(piKey);
}
if ((dwItemMask & FORMITEM_THEME_BASENAME)) {
char *baseName = 0;
IFORM_GetThemeBaseName(piForm, &baseName);
RootForm_UpdateTheme(me, baseName);
}
}
static boolean RootForm_ReplaceWidget(RootForm *me, IWidget **piw, IWidget *piwNew, IWidget *piwBefore)
{
int result = AEE_SUCCESS;
WidgetPos pos;
if (*piw) {
(void) IROOTCONTAINER_GetPos(me->piContainer, *piw, &pos);
(void) IROOTCONTAINER_Remove(me->piContainer, *piw);
IWIDGET_Release(*piw);
}
if (piwNew) {
result = IROOTCONTAINER_Insert(me->piContainer, piwNew, piwBefore, &pos);
if (result == AEE_SUCCESS) {
IWIDGET_AddRef(piwNew);
} else {
piwNew = NULL;
}
}
*piw = piwNew;
// Do an update since extents may have changed
RootForm_UpdateClientArea(me);
return (AEE_SUCCESS == result);
}
static int RootForm_SetThemeName(RootForm *me, const char *themeFile)
{
if (!me->piThemeFile)
return EBADSTATE;
FREEIF(me->themeFile);
me->themeFile = STRDUP(themeFile);
IRESFILE_Close(me->piThemeFile);
if (themeFile)
return IRESFILE_Open(me->piThemeFile, themeFile);
else
return AEE_SUCCESS;
}
static int RootForm_SetDisplay(RootForm *me, IDisplay *piDisplay)
{
int nErr = AEE_SUCCESS;
IDisplayCanvas *piCanvas = 0;
nErr = ISHELL_CreateInstance(FORM_SHELL(me), AEECLSID_DISPLAYCANVAS, (void **)&piCanvas);
if (!nErr) {
WExtent extent;
WidgetPos pos;
IDISPLAY_SetClipRect(piDisplay, NULL); // reset the clipping rectangle
IDISPLAY_GetClipRect(piDisplay, &me->rcContainer);
SETAEERECT(&me->rcClient, 0, 0, me->rcContainer.dx, me->rcContainer.dy);
IDISPLAYCANVAS_SetDisplay(piCanvas, piDisplay);
IROOTCONTAINER_SetCanvas(me->piContainer, (ICanvas *)piCanvas, &me->rcContainer);
if (me->piTitle) {
// Set extent, title is already positioned at 0, 0
IWIDGET_GetExtent(me->piTitle, &extent);
extent.width = me->rcContainer.dx;
IWIDGET_SetExtent(me->piTitle, &extent);
}
if (me->piBackground) {
// Set extent, background is already positioned at 0, 0
extent.width = me->rcContainer.dx;
extent.height = me->rcContainer.dy;
IWIDGET_SetExtent(me->piBackground, &extent);
}
if (me->piSoftkeys) {
// Set extent
IWIDGET_GetExtent(me->piSoftkeys, &extent);
extent.width = me->rcContainer.dx;
IWIDGET_SetExtent(me->piSoftkeys, &extent);
// And position at bottom of screen
IROOTCONTAINER_GetPos(me->piContainer, me->piSoftkeys, &pos);
pos.y = me->rcContainer.dy - extent.height;
IROOTCONTAINER_SetPos(me->piContainer, WIDGET_ZNORMAL, me->piSoftkeys, &pos);
}
}
RELEASEIF(piCanvas);
return nErr;
}
static void RootForm_ApplyTheme(RootForm *me)
{
int nrForms, i;
if (!me->piThemeFile)
return;
nrForms = IVECTORMODEL_Size(me->piForms);
for (i = 0; i < nrForms; i++) {
IForm *piForm;
char* pTheme = 0;
IVECTORMODEL_GetAt(me->piForms, i, (void **)&piForm);
IFORM_GetThemeBaseName(ROOTFORM_TO_IFORM(me), &pTheme);
pTheme = (pTheme) ? pTheme : "(None)";
BUIT_LOG("FORMS EVT: Apply Theme Started for %s", pTheme);
IFORM_ApplyTheme(piForm);
BUIT_LOG("FORMS EVT: Apply Theme Finished for %s", pTheme);
}
if (nrForms == 0) {
char *baseName = 0;
IFORM_GetThemeBaseName(ROOTFORM_TO_IFORM(me), &baseName);
#ifdef FEATURE_MOT_BREW
if (baseName != NULL) {
RootForm_UpdateTheme(me, baseName);
}
#else
RootForm_UpdateTheme(me, baseName);
#endif /*FEATURE_MOT_BREW*/
}
}
boolean RootForm_HandleEvent(IRootForm *po, AEEEvent evt, uint16 wParam, uint32 dwParam)
{
DECL(RootForm);
if (FORM_WIDGET(me)
&& IWIDGET_HandleEvent(FORM_WIDGET(me), evt, wParam, dwParam))
return TRUE;
if (evt == EVT_WDG_GETPROPERTY) {
switch(wParam) {
case FID_THEME_FNAME:
*(const char **)dwParam = me->themeFile;
return TRUE;
case FID_THEME_FILE:
*(IResFile **)dwParam = me->piThemeFile;
ADDREFIF(me->piThemeFile);
return TRUE;
case WID_TITLE:
*(IWidget **)dwParam = me->piTitle;
ADDREFIF(me->piTitle);
return TRUE;
case WID_SOFTKEYS:
*(IWidget **)dwParam = me->piSoftkeys;
ADDREFIF(me->piSoftkeys);
return TRUE;
case WID_BACKGROUND:
*(IWidget **)dwParam = me->piBackground;
ADDREFIF(me->piBackground);
return TRUE;
case WID_FORM:
IROOTCONTAINER_QueryInterface(me->piContainer, AEEIID_WIDGET, (void **)dwParam);
return TRUE;
case WID_CONTAINER:
*(IContainer **)dwParam = IROOTCONTAINER_TO_ICONTAINER(me->piContainer);
ADDREFIF(me->piContainer);
return TRUE;
default:
// Fall back on formbase
return Form_HandleEvent(ROOTFORM_TO_IFORM(me), evt, wParam, dwParam);
}
} else if (evt == EVT_WDG_SETPROPERTY) {
IForm *piForm = 0;
switch(wParam) {
case FID_ACTIVE:
piForm = IROOTFORM_GetTopForm(po);
if (piForm) {
// Activate or de-activate the top form
IFORM_SetProperty(piForm, FID_ACTIVE, dwParam);
}
// and invalidate root container on activation
if ((boolean)dwParam) {
IROOTCONTAINER_Invalidate(me->piContainer, 0, 0, 0);
}
return TRUE;
case FID_THEME:
RootForm_ApplyTheme(me);
return TRUE;
case FID_THEME_FNAME:
if (AEE_SUCCESS == RootForm_SetThemeName(me, (const char *)dwParam)) {
RootForm_ApplyTheme(me);
return TRUE;
}
return FALSE;
case FID_BACKGROUND:
// If we have a background widget, set the image into it
if (me->piBackground) {
IWIDGET_SetFormImage(me->piBackground, FORM_SHELL(me), (FormRes *)dwParam);
}
// Also load the image into our internal form, which will hold it as a default for other forms
return Form_HandleEvent(ROOTFORM_TO_IFORM(me), evt, wParam, dwParam);
case FID_DISPLAY:
return AEE_SUCCESS == RootForm_SetDisplay(me, (IDisplay *)dwParam);
case FID_WPROPS: {
WPropDesc *pdesc = (WPropDesc *)dwParam;
WResPropDesc wd;
wd.piResFile = me->piThemeFile;
if (pdesc) {
wd.args = pdesc->args;
wd.piWidget = pdesc->piWidget;
}
return IWIDGET_SetProperty(pdesc->piWidget, PROP_APPLYWPROPS, (uint32)&wd);
}
case WID_TITLE:
return RootForm_ReplaceWidget(me, &me->piTitle, (IWidget *)dwParam, WIDGET_ZNORMAL);
case WID_SOFTKEYS:
return RootForm_ReplaceWidget(me, &me->piSoftkeys, (IWidget *)dwParam, WIDGET_ZNORMAL);
case WID_BACKGROUND:
return RootForm_ReplaceWidget(me, &me->piBackground, (IWidget *)dwParam, WIDGET_ZBOTTOMMOST);
default:
// Fall back on formbase
return Form_HandleEvent(ROOTFORM_TO_IFORM(me), evt, wParam, dwParam);
}
}
// Non get/set property events are sent on to the topmost form
{
IForm *piForm = IROOTFORM_GetTopForm(po);
if (!piForm)
return FALSE;
else
return IFORM_HandleEvent(piForm, evt, wParam, dwParam);
}
}
static void RootForm_UpdateActiveListenerCB(RootForm *me, FormEvent *pEvent)
{
if (pEvent->base.evCode == EVT_MDL_FORM_CHANGE) {
RootForm_Update(me, pEvent->dwItemMask, pEvent->piForm);
}
}
static void RootForm_UpdateTopmostNonPopupListenerCB(RootForm *me, FormEvent *pEvent)
{
uint32 dwItemMask = pEvent->dwItemMask & (FORMITEM_BACKGROUND | FORMITEM_TITLE | FORMITEM_SOFTKEY);
if (pEvent->base.evCode == EVT_MDL_FORM_CHANGE && dwItemMask) {
RootForm_Update(me, dwItemMask, pEvent->piForm);
}
}
static void RootForm_ShowFormWidget(IRootForm *po, IForm *piForm, boolean bShow, boolean bFocus)
{
DECL(RootForm);
WidgetPos pos;
IWidget *piWidget;
if (!piForm)
return;
IFORM_GetWidget(piForm, WID_FORM, &piWidget);
if (!piWidget)
return;
// Set visibility
IROOTCONTAINER_GetPos(me->piContainer, piWidget, &pos);
pos.bVisible = bShow;
IROOTCONTAINER_SetPos(me->piContainer, piWidget, WIDGET_ZNORMAL, &pos);
// and set focus to the widget
if (bShow && bFocus) {
IWIDGET_MoveFocus(FORM_WIDGET(me), piWidget);
} else {
IWIDGET_MoveFocus(FORM_WIDGET(me), WIDGET_FOCUS_NONE);
}
IWIDGET_Release(piWidget);
}
/** Activates a given form. Previous form should have been
deactivated before this is called with bActivate set
*/
static void RootForm_ActivateForm(IRootForm *po, IForm *piForm, boolean bActivate)
{
DECL(RootForm);
if (!piForm)
return;
if (bActivate) {
// Undo the currently known active widget
RELEASEIF(me->piActiveWidget);
IFORM_GetWidget(piForm, WID_FORM, &me->piActiveWidget);
// Then go update all the items except the forms widget as this is not the
// form updating its own widget. Need to update first since theme information
// affect client area which affects form activation
RootForm_Update(me, FORMITEM_ALL & ~FORMITEM_WIDGET, piForm);
// then activate
IFORM_Activate(piForm);
} else {
IFORM_Deactivate(piForm);
}
}
static int RootForm_GetFormIndex(RootForm *me, IForm **ppiForm)
{
IForm *piForm;
int nrForms;
nrForms = IVECTORMODEL_Size(me->piForms);
if (nrForms > 0) {
if (*ppiForm == FORM_LAST || *ppiForm == FORM_DEFAULT) {
IVECTORMODEL_GetAt(me->piForms, nrForms - 1, (void **)ppiForm);
return nrForms - 1;
} else if (*ppiForm == FORM_FIRST) {
IVECTORMODEL_GetAt(me->piForms, 0, (void **)ppiForm);
return 0;
} else {
int i;
for (i = 0; i < nrForms; i++) {
IVECTORMODEL_GetAt(me->piForms, i, (void **)&piForm);
if (piForm == *ppiForm)
return i;
}
}
}
return -1;
}
static __inline int RootForm_GetFormInsertionIndex(RootForm *me, IForm **ppiForm)
{
int delta;
if (*ppiForm == FORM_FIRST)
return 0;
if (*ppiForm == FORM_LAST || *ppiForm == FORM_DEFAULT) {
delta = 1;
} else {
delta = 0;
}
return RootForm_GetFormIndex(me, ppiForm) + delta;
}
static void RootForm_StackChange(IRootForm *po)
{
DECL(RootForm);
IForm* piTopForm = IROOTFORM_GetTopForm(po);
LISTENER_Cancel(&me->mlFormActive);
LISTENER_Cancel(&me->mlFormTopmostNonPopup);
// If there are still forms on the stack, then we need to set up several things:
// 1. The topmost form is the active form
// 2. All other forms are not active
// 3. The topmost form is being listened to via mlFormActive
// 4. The topmost non-popup form is being listened to via mlFormTopmostNonPopup
// 5. The topmost non-popup form and all popup forms on top of it are shown
// 6. Forms below the topmost non-popup form are now shown
if (piTopForm)
{
boolean bFoundTopmostNonPopup = FALSE;
IModel* piModel = NULL;
IForm* pif;
// Logging stack change begin
BUIT_LOG("FORMS EVT: Stack Change Starting...", 1);
// Need to deal with the non-active forms first, then the active form
for (pif = piTopForm; pif; pif = IROOTFORM_GetForm(po, pif, FALSE, FALSE))
{
boolean bPopup;
bPopup = IFORM_GetIsPopup(pif);
IFORM_GetFormModel(pif, &piModel);
if (piModel)
{
if (pif != piTopForm)
{
RootForm_ShowFormWidget(po, pif, (boolean)(bFoundTopmostNonPopup? FALSE : TRUE), FALSE);
if (IFORM_IsActive(pif))
{
RootForm_ActivateForm(po, pif, FALSE);
}
}
if (!bPopup && !bFoundTopmostNonPopup)
{
IMODEL_AddListenerEx(piModel, &me->mlFormTopmostNonPopup, (PFNLISTENER)RootForm_UpdateTopmostNonPopupListenerCB, me);
if (pif != piTopForm)
// Only update if not the topmost form since the
// Activate below applies theme again The topmost
// non-popup (but not the top!) influences the
// background, title ans associated themes
RootForm_Update(me, FORMITEM_BACKGROUND | FORMITEM_TITLE | FORMITEM_THEME_BASENAME, pif);
bFoundTopmostNonPopup = TRUE;
}
}
RELEASEIF(piModel);
}
RootForm_ActivateForm(po, piTopForm, TRUE);
RootForm_ShowFormWidget(po, piTopForm, TRUE, TRUE);
IFORM_GetFormModel(piTopForm, &piModel);
if (piModel)
IMODEL_AddListenerEx(piModel, &me->mlFormActive, (PFNLISTENER)RootForm_UpdateActiveListenerCB, me);
RELEASEIF(piModel);
// Log that the form is about to be activated - all theme stuff has happened by now)
BUIT_LOG("FORMS EVT: Stack Change Finished", 1);
}
// Notify change in stack
Form_Notify(ROOTFORM_TO_FORM(me), FORMITEM_STACK);
}
int RootForm_InsertForm(IRootForm *po, IForm *piForm, IForm *pifBefore)
{
DECL(RootForm);
IWidget *piWidget = 0;
IWidget *piwBefore = 0;
IForm *pifCurrent;
int nrForms, formIndex, nErr;
if (!piForm)
return EBADPARM;
// Make sure we can insert, get the index we want to insert at
formIndex = RootForm_GetFormInsertionIndex(me, &pifBefore);
if (formIndex < 0)
return EBADPARM;
nrForms = IVECTORMODEL_Size(me->piForms);
pifCurrent = IROOTFORM_GetTopForm(po);
// Get widget to insert
IFORM_GetWidget(piForm, WID_FORM, &piWidget);
// Get widget insertion point.
if (formIndex == nrForms || !nrForms) {
piwBefore = WIDGET_ZTOPMOST;
} else if (pifBefore == FORM_FIRST) {
if (me->piBackground != NULL) {
// If we have a background widget, try to insert the form's widget
// above the background widget
piwBefore = IROOTCONTAINER_GetWidget(me->piContainer, me->piBackground, TRUE, FALSE);
if (piwBefore) {
// Add a reference, so it can be released below.
IWIDGET_AddRef(piwBefore);
}
}
if (!piwBefore) {
// No background widget, insert the form's widget at the bottom.
piwBefore = WIDGET_ZBOTTOMMOST;
}
} else {
IFORM_GetWidget(pifBefore, WID_FORM, &piwBefore);
}
// Make sure we have space for the new form
nErr = IVECTORMODEL_EnsureCapacity(me->piForms, MAX(FORMSTACK_MIN, nrForms + 1), FORMSTACK_GROW);
// Now insert
if (!nErr && piWidget && piwBefore) {
WidgetPos pos;
// Not really needed here since Activate does this to, but since
// we need to give a position on insert we may as well do it
// right
pos.x = me->rcClient.x;
pos.y = me->rcClient.y;
pos.bVisible = (piwBefore == WIDGET_ZTOPMOST);
// Insert widget into widget stack
nErr = IROOTCONTAINER_Insert(me->piContainer, piWidget, piwBefore, &pos);
}
if (!nErr) {
char* pTheme = 0;
// Add form to formstack
IVECTORMODEL_InsertAt(me->piForms, formIndex, piForm);
IFORM_AddRef(piForm);
// Set rootform
IFORM_SetProperty(piForm, FID_ROOT, (uint32)po);
// Log info
IFORM_GetThemeBaseName(ROOTFORM_TO_IFORM(me), &pTheme);
pTheme = (pTheme) ? pTheme : "(None)";
BUIT_LOG("FORMS EVT: Insert Set Theme Started for %s", pTheme);
// Set theme on new form
IFORM_ApplyTheme(piForm);
BUIT_LOG("FORMS EVT: Insert Set Theme Finished for %s", pTheme);
//RootForm_Update(me, FORMITEM_THEME, piForm);
RootForm_StackChange(po);
}
RELEASEIF(piWidget);
if (piwBefore != WIDGET_ZTOPMOST && piwBefore != WIDGET_ZBOTTOMMOST)
RELEASEIF(piwBefore);
return nErr;
}
int RootForm_RemoveForm(IRootForm *po, IForm *piForm)
{
DECL(RootForm);
IWidget *piWidget = 0;
IForm *piF = 0;
int nrForms = 0;
int formIndex;
boolean bOnlyPopups = 1;
if (me->piForms)
nrForms = IVECTORMODEL_Size(me->piForms);
if (piForm == FORM_ALL) {
while (nrForms > 0) {
IROOTFORM_RemoveForm(po, FORM_LAST);
nrForms = IVECTORMODEL_Size(me->piForms);
}
} else {
formIndex = RootForm_GetFormIndex(me, &piForm);
if (formIndex < 0)
return EBADPARM;
IFORM_GetWidget(piForm, WID_FORM, &piWidget);
if (piWidget) {
IROOTCONTAINER_Remove(me->piContainer, piWidget);
}
// Hide form widget
RootForm_ShowFormWidget(po, piForm, FALSE, FALSE);
// Deactivate form
RootForm_ActivateForm(po, piForm, FALSE);
// Tell it of rootform departure
IFORM_SetProperty(piForm, FID_ROOT, 0);
// Delete it from the stack
IVECTORMODEL_DeleteAt(me->piForms, formIndex);
RootForm_StackChange(po);
RELEASEIF(piWidget);
// Now many forms do we now have?
nrForms = IVECTORMODEL_Size(me->piForms);
}
// Cycle through remaining forms to determine type
for (piF = IROOTFORM_GetTopForm(po); piF && bOnlyPopups; piF = IROOTFORM_GetForm(po, piF, FALSE, FALSE))
{
bOnlyPopups &= IFORM_GetIsPopup(piF);
}
if ((0 == nrForms) || bOnlyPopups)
{
// If we don't have any more forms, or the only forms we do have are popups,
// ensure the title has been cleaned (the title memory is owned by the last full screen form,
// which may no longer exist).
if (me->piTitle) {
// Release image. Text is owned by form
RELEASEIF(me->titleInfo.piImage);
me->titleInfo.pwText = NULL;
// Set title info
IWIDGET_SetImageStaticInfo(me->piTitle, &me->titleInfo, 0);
}
}
if (0 == nrForms) {
// There are no more forms, ensure the softkey labels
// have been cleaned (the softkey memory is owned by the form, which may no
// longer exist).
if (me->piSoftkeys) {
IWidget *piKey = NULL;
(void) IWIDGET_GetSoftkey(me->piSoftkeys, PROP_SOFTKEY1, &piKey);
if (piKey) {
IWIDGET_SetText(piKey, NULL, 0);
IWIDGET_Release(piKey);
piKey = NULL;
}
(void) IWIDGET_GetSoftkey(me->piSoftkeys, PROP_SOFTKEY2, &piKey);
if (piKey) {
IWIDGET_SetText(piKey, NULL, 0);
IWIDGET_Release(piKey);
piKey = NULL;
}
}
} else {
RootForm_Update(me, FORMITEM_THEME_BASENAME, IROOTFORM_GetTopForm(po));
}
return AEE_SUCCESS;
}
void RootForm_GetClientRect(IRootForm *po, IXYContainer **ppo, AEERect *rc)
{
DECL(RootForm);
if (rc) {
*rc = me->rcClient;
}
if (ppo && me->piContainer) {
*ppo = IROOTCONTAINER_TO_IXYCONTAINER(me->piContainer);
IROOTCONTAINER_AddRef(me->piContainer);
}
}
IForm *RootForm_GetForm(IRootForm *po, IForm *pifRef, boolean bNext, boolean bWrap)
{
DECL(RootForm);
IForm *piForm = 0;
int nrForms, formIndex;
if (me->piForms == NULL)
return NULL;
nrForms = IVECTORMODEL_Size(me->piForms);
if (pifRef == NULL) {
formIndex = bNext ? 0 : nrForms - 1;
IVECTORMODEL_GetAt(me->piForms, formIndex, (void **)&piForm);
return piForm;
}
formIndex = RootForm_GetFormIndex(me, &pifRef);
if (formIndex < 0)
return NULL;
formIndex += bNext ? 1 : -1;
if (formIndex < 0) {
formIndex = bWrap ? nrForms - 1 : -1;
} else if (formIndex >= nrForms) {
formIndex = bWrap ? 0 : - 1;
}
if (formIndex < 0)
return NULL;
IVECTORMODEL_GetAt(me->piForms, formIndex, (void **)&piForm);
return piForm;
}
int RootForm_ResolveForm(IRootForm *po, char const *szFormUrl, IForm **ppiForm)
{
DECL(RootForm);
IWebUtil *piWebUtil = 0;
AEECLSID formClsId;
int result;
UrlParts parts;
char *path = 0;
if (!ppiForm || !szFormUrl)
return EBADPARM;
// Assume failure
*ppiForm = 0;
// Parse the URL
result = ISHELL_CreateInstance(FORM_SHELL(me), AEECLSID_WEBUTIL, (void **) &piWebUtil);
if (result == 0)
result = IWEBUTIL_ParseUrl(piWebUtil, szFormUrl, &parts);
// Check the scheme
if (result == 0
&& (!UP_HASSCHM(&parts) || STRNCMP(parts.cpcSchm,FORM_URL_SCHEME,sizeof(FORM_URL_SCHEME)-1)))
result = ESCHEMENOTSUPPORTED;
// Do we have a path?
if (result == 0
&& (!UP_HASPATH(&parts) || UP_PATHLEN(&parts) <= 0))
result = ESCHEMENOTSUPPORTED;
// Extract the path (we need it to be NULL terminated)
if (result == 0
&& 0 == (path = MALLOC(UP_PATHLEN(&parts)+1)))
result = ENOMEMORY;
if (result == 0) {
STRNCPY(path, parts.cpcHost, UP_PATHLEN(&parts)+1);
// Does a handler exist for this path, of type AEEIID_FORM?
if (0 == (formClsId = ISHELL_GetHandler(FORM_SHELL(me), AEEIID_FORM, path)))
// Nope...
result = ESCHEMENOTSUPPORTED;
}
if (result == 0)
// Got the actual class id, lets create the form
result = ISHELL_CreateInstance(FORM_SHELL(me), formClsId, (void **) ppiForm);
//
// TODO: We could use IWEBUTIL_ParseFormFields() to parse parts.cpcSrch
// for known Form properties and apply them here...
RELEASEIF(piWebUtil);
FREEIF(path);
return result;
}
void RootForm_Dtor(RootForm *me)
{
IROOTFORM_RemoveForm(ROOTFORM_TO_IROOTFORM(me), FORM_ALL);
RELEASEIF(me->piTitle);
RELEASEIF(me->piSoftkeys);
RELEASEIF(me->piContainer);
RELEASEIF(me->piBackground);
RELEASEIF(me->titleInfo.piImage);
RELEASEIF(me->piForms);
RELEASEIF(me->piActiveWidget);
RELEASEIF(me->piThemeFile);
FREEIF(me->themeFile);
Form_Dtor(&me->base);
}
uint32 RootForm_Release(IRootForm *po)
{
DECL(RootForm);
if (FORM_NREFS(me) == 1)
RootForm_Dtor(me);
return Form_Release(IROOTFORM_TO_IFORM(po));
}
int RootForm_QueryInterface(IRootForm *po, AEECLSID clsid, void **ppo)
{
if (clsid == AEEIID_ROOTFORM) {
*ppo = po;
Form_AddRef(IROOTFORM_TO_IFORM(po));
return AEE_SUCCESS;
}
return Form_QueryInterface(IROOTFORM_TO_IFORM(po), clsid, ppo);
}
int RootForm_Construct(RootForm *me, AEEVTBL(IRootForm) *pvt, IModule *piModule, IShell *piShell)
{
int result;
WExtent extent;
WidgetPos pos;
IDisplay *piDisplay = 0;
ICanvas *piCanvas = 0;
Form_Ctor(&me->base, (AEEVTBL(IForm) *)pvt, piModule, piShell,
(PFNHANDLER)RootForm_HandleEvent);
pos.x = 0;
pos.y = 0;
pos.bVisible = TRUE;
SETWEXTENT(&extent, 0, 0);
// Form overrides
pvt->Release = RootForm_Release;
pvt->QueryInterface = RootForm_QueryInterface;
// RootForm definitions
pvt->InsertForm = RootForm_InsertForm;
pvt->RemoveForm = RootForm_RemoveForm;
pvt->GetClientRect = RootForm_GetClientRect;
pvt->GetForm = RootForm_GetForm;
pvt->ResolveForm = RootForm_ResolveForm;
result = ISHELL_CreateInstance(piShell, AEECLSID_VECTORMODEL, (void **)&me->piForms);
if (result == 0) {
IVECTORMODEL_SetPfnFree(me->piForms, (PFNNOTIFY)RootForm_FreeFormEntry);
result = ISHELL_CreateInstance(piShell, AEECLSID_DISPLAY, (void **)&piDisplay);
}
if (result == 0)
result = ISHELL_CreateInstance(piShell, AEECLSID_ROOTCONTAINER, (void **)&me->piContainer);
if (result == 0)
result = IROOTCONTAINER_QueryInterface(me->piContainer, AEEIID_WIDGET, (void **)&me->base.piWidget);
if (result == 0)
result = ISHELL_CreateInstance(piShell, AEECLSID_RESFILE, (void **)&me->piThemeFile);
if (result == 0)
result = ISHELL_CreateInstance(piShell, AEECLSID_IMAGEWIDGET, (void **)&me->piBackground);
if (result == 0) {
IWIDGET_SetFlags(me->piBackground, IDF_ALIGN_RIGHT | IDF_ALIGN_BOTTOM);
// Insert, extent will be fixed up in SetDisplay below
result = IROOTCONTAINER_Insert(me->piContainer, me->piBackground, WIDGET_ZBOTTOMMOST, &pos);
}
if (result == 0)
// Construct title
result = ISHELL_CreateInstance(piShell, AEECLSID_IMAGESTATICWIDGET, (void **)&me->piTitle);
if (result == 0) {
extent.height = 15;
// Set title font to bold by default. Apps and themes can override it.
IWIDGET_SetFontClass(me->piTitle, AEECLSID_FONTSYSBOLD);
IWIDGET_SetShadowOffsetY(me->piTitle, 0);
IWIDGET_SetBorderWidth(me->piTitle, 0);
IWIDGET_SetExtent(me->piTitle, &extent);
// Add to container
result = IROOTCONTAINER_Insert(me->piContainer, me->piTitle, WIDGET_ZTOPMOST, &pos);
}
if (result == 0)
// Construct Softkeys
result = ISHELL_CreateInstance(piShell, AEECLSID_SOFTKEYWIDGET, (void **)&me->piSoftkeys);
if (result == 0) {
IWIDGET_SetShadowOffsetY(me->piSoftkeys, -1);
IWIDGET_SetBorderWidth(me->piSoftkeys, 0);
IWIDGET_SetExtent(me->piSoftkeys, &extent);
IWIDGET_SetLeftPadding(me->piSoftkeys, 2);
IWIDGET_SetRightPadding(me->piSoftkeys, 2);
// Insert at 0, 0. Correct positioning will happen in SetDisplay
result = IROOTCONTAINER_Insert(me->piContainer, me->piSoftkeys, WIDGET_ZTOPMOST, &pos);
}
if (result == 0)
result = RootForm_SetDisplay(me, piDisplay);
if (result == 0) {
char* pTheme = 0;
IFORM_SetThemeBaseName(ROOTFORM_TO_IFORM(me), "Root");
IFORM_GetThemeBaseName(ROOTFORM_TO_IFORM(me), &pTheme);
pTheme = (pTheme) ? pTheme : "(None)";
BUIT_LOG("FORMS EVT: Construct Set Theme Started for %s", pTheme);
IROOTFORM_SetThemeFileName(ROOTFORM_TO_IROOTFORM(me), "theme.bar");
BUIT_LOG("FORMS EVT: Construct Set Theme Finished for %s", pTheme);
} else {
RootForm_Dtor(me);
}
RELEASEIF(piDisplay);
RELEASEIF(piCanvas);
return result;
}
int RootForm_New(IRootForm **ppo, IModule *piModule, IShell *piShell)
{
RootForm *me = MALLOCREC_VTBL(RootForm, IRootForm);
int result;
*ppo = (IRootForm *)me;
if (!me)
return ENOMEMORY;
result = RootForm_Construct(me, GETVTBL(me, IRootForm), piModule, piShell);
if (result != 0) {
*ppo = NULL;
FREE(me);
}
return result;
}
小结:
注释应该准确易懂,防止二义性,错误的注释有害无利注释是对代码的提示,避免臃肿和喧宾夺主一目了然的代码避免加注释
不要用缩写来注释代码,这样可能会产生误解
注释用于阐述原因和意图而不是描述程序的运行过程
第 13 课 接续符合转义符
接续符的意义
c语言中的接续符(\)是指示编译器行为的利器
#in\
clude\ e <st\
dio.h>
in\
tm\
ain(\
)
{
pri\
ntf\
(\
“hello world\n” )\
;
ret\ urn 0;
}
例 1 初探接续符
接续符的作用
编译器会将反斜杠剔除,跟在反斜杠后面的字符自动接续到前一行
在接续单词时,反斜杠之后不能有空格,反斜杠的下一行之前也不能有空格接续符适合在定义宏代码块时使用
例 2 接续符的应用
#include <stdio.h>
#define SWAP(a,b) \
{ \
int temp = a; \
a = b; \
b = temp; \
}
int main()
{
int a = 1;
int b = 2;
int c = 3;
SWAP(a,b);
printf("a = %d, b = %d\n", a, b);
SWAP(b,c);
printf("b = %d, c = %d\n", b, c);
return 0;
}
转义符的意义
c语言中的转义符(\)主要用于表示无回显字符,也可用于表示常规字符
\n 回车换行
\t 横向跳到下一制表位置
\v 竖向跳格
\b 退格
\r 回车
\f 走纸换页
\ 反斜杠符”\”
\’ 串引号符
\a 响铃
\ddd 1~3 位八进制所代码的字符
\xhh 1~2 位十六进制所代表的字符
转义符的使用
当反斜杠(\)作为转义符使用时必须出现在单引号或者双引号之间
char enter=’\n’;
char *p=”\141\t\x63”;
例 3 转义符使用实例
#include <stdio.h>
int main()
{
char enter = '\n';
char* p = "\141\t\x62";
printf("%s", p);
printf("%c", enter);
return 0;
}
小结:
c语言中的反斜杠(\)同时具有接续符和转义符的作用作为接续符使用时可直接出现在程序中
作为转义符使用时需出现在单引号或双引号之间
akari
第 14 课 单引号和双引号
单引号和双引号
c语言中的单引号用来表示字符字面量
c语言中的双引号用来表示字符串字面量‘a’表示字符字面量
在内存中占 1 个字节
‘a’+1 表示‘a’的 ASCII 码加 1,结果为‘b’
“a”表示字符串字面量
在内存中占 2 个字节
“a”+1 表示指针运算,结果指向“a”结束符‘\0’
下面程序段合法吗?
char *p1=1;
char *p2=’1’;
char *p3=”1”;
例 1 单引号和双引号的本质
#include <stdio.h>
int main(void)
{
char *p1=1;
char *p2='1';
char *p3="1";
char *p4="hello world";
char *p5="2";
//printf("%s,%s,%s",p1,p2,p3);
//printf('\n');
printf("%s\n",p4);
printf("%s\n",p5);
printf("%s\n",p3);
printf("%c\n",p2);
//printf("%s\n",p1);
printf("%d\n",p1);
printf("\n");
return 0;
}
小贴士:
字符字面量被编译为对应的 ASCII 码
字符串字面量被编译为对应的内存地址
printf 的第一个参数被当成字符串内存地址
内存的低地址空间不能在程序中随意访问
例 2 混淆概念的代码
#include <stdio.h>
int main()
{
char c = " ";
while( (c == "\t") || (c == " ") || (c == "\n") )
{
scanf("%c", &c);
}
return 0;
}
char c=”string”;
发生了什么?
分析:
1)编译后字符串“string”的内存地址被赋值给变量 c
2)内存地址占用 4 个字节,而变量 c 只占用 1 个字节
3)由于类型不同,赋值后产生截断
小结:
单引号括起来的单个字符代表整数
双引号括起来的字符代表字符指针
c编译器接受字符和字符串的比较,无任何意义
c编译器允许字符串对字符变量赋值,只能得到错误
第15 课 逻辑运算符分析
逻辑运算符&&,||和!真的很简单吗?
下面的程序运行结束后,i,j,k 的值分别为多少?
int i=0;
int j=0;
int k=0;
++i || ++j && ++k;
例 1 初探逻辑运算符
#include <stdio.h>
int main()
{
int i = 0;
int j = 0;
int k = 0;
++i || ++j && ++k;
printf("%d\n", i);
printf("%d\n", j);
printf("%d\n", k);
return 0;
}
逻辑运算符分析
程序中的短路
||从左向右开始计算:
当遇到为真的条件时停止计算,整个表达式为真
所有条件为假时表达式才为假
&&从左向右开始计算:
当遇到为假的条件时停止计算,整个表达式为假
所有条件为真时表达式为真
逻辑表达式中,&&比||具有更高的优先级
++i || ++j && ++k;
↓
(++i) || (++j && ++k);
↓
(true && ++i) || (++j && ++k);
在&&和||混合运算时,整个表达式被看作||表达式,编译器从左向右开始计算&&表达式,当某个&&表达式为真时,停止计算,整个表达式的值为真。
例 2 程序中的短路规则
#include <stdio.h>
int g = 0;
int f()
{
printf("In f()...\n");
return g++;
}
int main()
{
if( g || f() && f() )
{
printf("In if statement: %d\n", g);
}
printf("In main(): %d\n", g);
return 0;
}
“!”究竟是神马?
int main()
{
printf(“%d\n”,!0);
printf(“%d\n”,!1);
printf(“%d\n”,!-100);
return 0;
}
c语言中的逻辑非“!”只认得 0,只知道见了 0 就返回 1。因此当其碰见的值不是 0 时,其结果为 0。
例 3 逻辑非运算符的使用
#include <stdio.h>
int main()
{
printf("%d\n", !0);
printf("%d\n", !1);
printf("%d\n", !100);
printf("%d\n", !-1000);
return 0;
}
小结:
程序中的逻辑表达式遵从短路规则
在&&与||混合运算时:
整个表达式被看作||表达式
从左向右先计算&&表达式
最后计算||表达式
逻辑非!运算符只认得 0
碰见 0 返回 1,否则统统返回 0
只有 0 才代表假,其余的所有值均代表真
第 16 课 位运算符分析
c语言中的位运算符
位运算符直接对 bit 位进行操作,其效率最高
& 按位与
| 按位或
^ 按位异或
~ 取反
<< 左移
右移
位运算符分析
左移和右移注意点
左移操作数必须为整数类型
char 和 short 被隐式转换为 int 后进行移位操作
右操作数的范围必须为:[0,31]
左移运算符<<将运算数的二进制位左移
规则:高位丢弃,低位补 0
右移运算符>>把运算数的二进制位右移
规则:高位补符号位,低位丢弃
有趣的问题
0x1<<2+3 的值会是多少?
A 同学:先算出 0x1<<2 再把中间结果加 3,最终为 7
B 同学:我觉得先算出 2+3,所以结果为 32
C同学:可以这么混合计算吗?问题:
原作者的本意究竟想表达什么?
例 1 位运算符初探
#include <stdio.h>
int main()
{
printf("%d\n", 3 << 2);
printf("%d\n", 3 >> 1);
printf("%d\n", -1 >> 1);
printf("%d\n", 0x01 << 2 + 3);
printf("%d\n", 3 << -1); // oops!
return 0;
}
小贴士
防错准则:
避免位运算符,逻辑运算符和数学运算符同时出现在一个表达式中
当位运算符,逻辑运算符和数学运算符需要同时参与运算时,尽量使用括号()来表达计算次序
小技巧:
左移 n 位相当于乘以 2 的 n 次方,但效率比数学运算符高右移 n 位相当于除以 2 的 n 次方,但效率比数学运算符高
例 2 交换两个整型变量的值
#include <stdio.h>
#define SWAP1(a, b) \
{ \
int t = a; \
a = b; \
b = t; \
}
#define SWAP2(a, b) \
{ \
a = a + b; \
b = a - b; \
a = a - b; \
}
#define SWAP3(a, b) \
{ \
a = a ^ b; \
b = a ^ b; \
a = a ^ b; \
}
int main()
{
int a = 1;
int b = 2;
printf("a = %d\n", a);
printf("b = %d\n", b);
SWAP3(a ,b);
printf("a = %d\n", a);
printf("b = %d\n", b);
return 0;
}
位运算与逻辑运算不同:
位运算没有短路规则,每个操作数都参与运算
位运算的结果为整数,而不是 0 或 1
位运算优先级高于逻辑运算优先级
例 3 混淆概念的判断条件
#include <stdio.h>
int main()
{
int i = 0;
int j = 0;
int k = 0;
if( ++i | ++j & ++k )
{
printf("Run here...\n");
}
return 0;
}
小结:
位运算符只能用于整数类型
左移和右移运算符的右操作数范围必须为[0,31]
位运算没有短路规则,所有操作数均为求值
位运算的效率高于四则运算和逻辑运算
运算优先级:四则运算>位运算>逻辑运算
第 17 课 ++和—操作符分析
++,–操作符的本质
++和—操作符对应两条汇编指令
前置
变量自增(减)1
取变量值
后置
取变量值
变量自增(减)1
++,–操作符使用分析
一对令人头疼的兄弟
int i=0
(i++)+(i++)+(i++);
(++i)+(++i)+(++i);
你觉得这两个表达式的值分别是多少?
例1 令人头疼的兄弟++,–操作符使用分析
#include <stdio.h>
int main()
{
int i = 0;
int r = 0;
r = (i++) + (i++) + (i++);
printf("i = %d\n", i);
printf("r = %d\n", r);
r = (++i) + (++i) + (++i);
printf("i = %d\n", i);
printf("r = %d\n", r);
return 0;
}
c语言中只规定了++和—对应指令的相对执行次序
++和—对应的汇编指令不一定连续运行
在混合运算中,++和—的汇编指令可能被打断执行
++和—参与混合运算结果是不确定的
笔试中的“奇葩”题
1、++i+++i+++i
2、a+++b
a++ +b
a+ ++b
编译器究竟如何解释?
贪心法:++,–表达式的阅读技巧
编译器处理的每个符号应该尽可能多的包含字符
编译器从左向右的顺序一个一个尽可能多的读入字符当读入的字符不可能和已读入的字符组成合法符号为止
例 2 贪心法阅读示例
#include <stdio.h>
int main()
{
int i = 0;
//int j = ++i+++i+++i; //++i++-->1++
int a = 1;
int b = 4;
int c = a+++b; //a+++;1+4==>5 a===>2
int* p = &a;
b = b / *p;
printf("i = %d\n", i);
//printf("j = %d\n", j);
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
return 0;
}
空格作为 c 语言中一个完整符号的休止符
编译器读入空格后立即对之前读入的符号进行处理
小结:
++和—操作符在混合运算中的行为可能不同
编译器通过贪心法处理表达式中的子表达式
空格可以作为 c 语言中一个完整符号的休止符
编译器读入空格后立即对之前读入的符号进行处理
第 18 课 三目运算符和逗号表达式
三目运算符
三目运算符(a?b:c)可以作为逻辑运算的载体
规则:当 a 的值为真时,返回 b 的值;否则返回 c 的值
下面的程序运行结束后,a,b,c 的值分别为多少?
int a=1;
int b=2;
int c=0;
c=a
#include <stdio.h>
int main()
{
int i = 0;
//int j = ++i+++i+++i; //++i++-->1++
int a = 1;
int b = 4;
int c = a+++b; //a+++;1+4==>5 a===>2
//int* p = &a;
//b = b / *p;
*(a<b?&a:&b)=3;
printf("i = %d\n", i);
//printf("j = %d\n", j);
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
return 0;
}
三目运算符(a?b:c)的返回类型
通过隐式类型转换规则返回 b 和 c 的较高类型
当b 和 c 不能隐式转换到同一类型时将编译出错判断下面三目表达式的返回类型
char c=0; short s=0; int i=0; double d=0; char *p=”str”;
printf(“%d\n”,sizeof(c?c:s));
printf(“%d\n”,sizeof(i?i:d));
printf(“%d\n”,sizeof(d?d:p));
例 2 三目运算符的返回类型
#include <stdio.h>
int main()
{
char c = 0;
short s = 0;
int i = 0;
double d = 0;
char* p = "str";
printf( "%d\n", sizeof(c ? c : s) ); //4
printf( "%d\n", sizeof(i ? i : d) ); //8
//printf( "%d\n", sizeof(d ? d : p) ); //error
return 0;
}
逗号表达式
逗号表达式是 c 语言中的“粘贴剂”
逗号表达式用于将多个子表达式连接为一个表达式逗号表达式的值为最后一个字表达式的值
逗号表达式中的前 N+1 个子表达式可以没有返回值
逗号表达式按照从左向右的顺序计算每个子表达式的值
exp1,exp2,exp3,….,expN
下面程序输出什么?为什么?
int i=0;
while(i<5)
printf(“i=%d\n”,i)
i++;
例3 逗号表达式的示例
#include <stdio.h>
void hello()
{
printf("Hello!\n");
}
int main()
{
int a[3][3] = {
(0, 1, 2),
(3, 4, 5),
(6, 7, 8)
};
int i = 0;
int j = 0;
while( i < 5 )
printf("i = %d\n", i),
hello(),
i++;
for(i=0; i<3; i++)
{
for(j=0; j<3; j++)
{
printf("a[%d][%d] = %d\n", i, j, a[i][j]);
}
}
return 0;
}
例4 一行代码实现 strlen
#include <stdio.h>
#include <assert.h>
int strlen(const char* s)
{
return assert(s), (*s ? strlen(s + 1) + 1 : 0);
}
int main()
{
printf("len = %d\n", strlen("Delphi"));
printf("len = %d\n", strlen(NULL));
return 0;
}
小结:
三目运算符返回变量的值,而不是变量本身
三目运算符通过隐式类型转换规则确认返回值类型
逗号表达式按照从左向右的顺序计算每个子表达式的值逗号表达式的值为最后一个字表达式的值
第 19 课 编译过程简介
初识编译器
编译器:预处理器,编译器,汇编器,链接器
你不知道的事
file.c,file.h->预处理器->file.i->编译器->file.s->汇编器->file.o
预处理
处理所有的注释,以空格代替
将所有的#define 删除,并且展开所有的宏定义
处理条件编译指令#if,#ifdef,#elif,#else,#endif
处理#include,展开被包含的文件
保留编译器需要使用的#pragma 指令
预处理指令示例
gcc –E file.c –o file.i
编译
对预处理文件进行词法分析,语法分析和语义分析
词法分析:分析关键字,标识符,立即数等是否合法语法分析:分析表达式是否遵循语法规则
语义分析:在语法分析的基础上进一步分析表达式是否合法分析结束后进行代码优化生成相应的汇编代码文件
编译指令示例
gcc –S file.i –o file.s
汇编
汇编器将汇编代码转变为机器的可以执行的指令
每条汇编语句几乎都对应一条机器指令
汇编指令示例
gcc –c file.s –o file.o
例 1 源代码单步编译示例
/*
This is a header file.
*/
char* p = "Delphi";
int i = 0;
#include "19-1.h"
// Begin to define macro
#define GREETING "Hello world!"
#define INC(x) x++
// End
int main()
{
p = GREETING;
INC(i);
return 0;
}
小结:
编译过程中分为预处理,编译,汇编和链接四个阶段
预处理:处理注释,宏以及已经以#开头的符号
编译:进行词法分析,语法分析和语义分析等
汇编:将汇编代码翻译为机器指令的目标文件
第 20 课 链接过程简介
问题:工程中的每个 c 语言源文件被编译后生产目标文件,这些文件如何生存最终的可执行程序?
选择器的主要作用是把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确的
链接。
静态链接
由链接器在链接时将库的内容直接加入到可执行程序中
file1.o,file2.o,libc.a链接器->a.out
Linux 下静态库的创建和使用
编译静态库源码:gcc –c lib.c –o lib.o
生成静态库文件:ar –q lib.a lib.o
使用静态库编译:gcc main.c lib.a –o main.out
例 1 静态链接示例
#include <stdio.h>
extern char* name();
extern int add(int a, int b);
int main()
{
printf("Name: %s\n", name());
printf("Result: %d\n", add(2, 3));
return 0;
}
char* name()
{
return "Static Lib";
}
int add(int a, int b)
{
return a + b;
}
模块链接
动态链接
可执行程序在运行时才动态加载库进行链接
库的内容不会进入可执行程序中
lib1.so-stub1,lib2.so-stub2链接器 linker->a.out
Linux 下动态库的创建和使用
编译动态库源码:gcc –shared dlib.c –o dlib.so
使用动态库编译:gcc main.c –ldl main.out
关键系统调用
dlopen:打开动态库文件
dlsym:查找动态库中的函数并返回调用地址
dlclose:关闭动态库文件
例 2 动态链接示例
#include <stdio.h>
#include <dlfcn.h>
int main()
{
void* pdlib = dlopen("./dlib.so", RTLD_LAZY);
char* (*pname)();
int (*padd)(int, int);
if( pdlib != NULL )
{
pname = dlsym(pdlib, "name");
padd = dlsym(pdlib, "add");
if( (pname != NULL) && (padd != NULL) )
{
printf("Name: %s\n", pname());
printf("Result: %d\n", padd(2, 3));
}
dlclose(pdlib);
}
else
{
printf("Cannot open lib ...\n");
}
return 0;
}
char* name()
{
return "Dynamic Lib";
}
int add(int a, int b)
{
return a + b;
}
小结:
链接是指将目标文件最终链接为可执行程序
根据链接方式的不同,链接过程可以分为:
静态链接:目标文件直接连接进入可执行程序
动态链接:在程序启动才动态加载目标文件
第 21 课 宏定义与使用分析
c语言中的宏定义
#define 是预处理器处理的单元实体之一
#define 定义的宏可以出现在程序的任意位置
#define 定义之后的代码都可以使用这个宏
定义宏常量
#define 定义的宏常量可以直接使用
#define 定义的宏常量本质为字面量
下面宏定义正确吗?
#define ERROR-1
#define PATH1 “D:\test\test.c”
#define PATH2 “D:\test\test.c”
#define PATH3 “D:\test\”
test.c
#define ERROR -1
#define PATH1 "D:\test\test.c"
#define PATH2 D:\test\test.c
#define PATH3 D:\test\
test.c
int main()
{
int err = ERROR;
char* p1 = PATH1;
char* p2 = PATH2;
char* p3 = PATH3;
}
例 1 宏定义分析
宏定义表达式
#define 表达式的使用类似函数调用
#define 表达式可以比函数更强大
#define 表达式比函数更容易出错
下面宏表达式定义正确吗?
#define_SUM(a,b) (a)+(b)
#define_MIN(a,b) ((a)<(b)?(a)(b))
#define_DIM_(a) sizeof(a)/sizeof(*a)
例 2 宏表达式分析
// #include <stdio.h>
#define _SUM_(a, b) (a) + (b)
#define _MIN_(a, b) ((a) < (b) ? (a) : (b))
#define _DIM_(a) sizeof(a)/sizeof(*a)
int main()
{
int a = 1;
int b = 2;
int c[4] = {0};
int s1 = _SUM_(a, b);
int s2 = _SUM_(a, b) * _SUM_(a, b);
int m = _MIN_(a++, b);
int d = _DIM_(c);
// printf("s1 = %d\n", s1);
// printf("s2 = %d\n", s2);
// printf("m = %d\n", m);
// printf("d = %d\n", d);
return 0;
}
宏表达式与函数的对比
宏表达式被预处理处理,编译器不知道宏表达式的存在宏表达式用“实参”完全代替形参,不进行任何运算宏表达式没有任何的“调用”开销宏表达式中不能出现递归定义
#define_SUM_(n)((n>0)?(_SUM_(n-1)+n):0)
int s=_SUM_(10);
有趣的问题
宏定义的常量或表达式是否有作用域限制?
下面的程序合法吗?
void def()
{
#define PI 3.1415926
#define AREA(r) r*r*PI
}
double area(double r)
{
return AREA(r);
}
例 3 宏的作用域分析
// #include <stdio.h>
void def()
{
#define PI 3.1415926
#define AREA(r) r * r * PI
}
double area(double r)
{
return AREA(r);
}
int main()
{
double r = area(5);
// printf("PI = %f\n", PI);
// printf("d = 5; a = %f\n", r);
return 0;
}
强大的内置宏含义示例
“`
FILE 被编译的文件名 file1.c
_LINE_ 当前行号 25
_DATE_ 编译时的日期 Jan 31 2012
_TIME_ 编译的时间 17:01:01
_STDC_ 编译器是否遵循标准 c 规范 1
例 4 宏使用综合示例
#include <stdio.h>
#include <malloc.h>
#define MALLOC(type, x) (type*)malloc(sizeof(type)*x)
#define FREE(p) (free(p), p=NULL)
#define LOG(s) printf("[%s] {%s:%d} %s \n", __DATE__, __FILE__, __LINE__, s)
#define FOREACH(i, m) for(i=0; i<m; i++)
#define BEGIN {
#define END }
int main()
{
int x = 0;
int* p = MALLOC(int, 5);
LOG("Begin to run main code...");
FOREACH(x, 5)
BEGIN
p[x] = x;
END
FOREACH(x, 5)
BEGIN
printf("%d\n", p[x]);
END
FREE(p);
LOG("End");
return 0;
}
小结:
预处理器直接对宏进行文本替换
宏使用时的参数不会进行求值和运算
预处理器不会对宏定义进行语法检查
宏定义时出现的语法错误只能被编译器检测
宏定义的效率高于函数调用
宏的使用会带来一定的副作用
第 22 课 条件编译使用分析
基本概念
条件编译的行为类似于 c 语言中的 if。。。else。。。
条件编译时预编译指示命令,用于控制是否编译某段代码
#define C 1
int main()
{
#if(C==1)
printf(“This is first printf…\n”);
#else
printf(“This is second printf…\n”);
#endif
return 0;
}
例 1 条件编译初探
// #include <stdio.h>
#define C 1
int main()
{
const char* s;
#if( C == 1 )
s = "This is first printf...\n";
#else
s = "This is second printf...\n";
#endif
// printf("%s", s);
return 0;
}
条件编译的本质
预编译器根据条件编译指令有选择的删除代码
编译器不知道代码分支的存在
if。。。else。。。语句在运行期进行分支判断
条件编译指令在预编译期进行分支判断
可以通过命令行定义宏
gcc –Dmacro=value file.c
或gcc –Dmacro file.c
例 2 通过命令行定义宏
//#include <stdio.h>
int main()
{
const char* s;
#ifdef C
s = "This is first printf...\n";
#else
s = "This is second printf...\n";
#endif
//printf("%s", s);
return 0;
}
#include 的本质
#include 的本质是将已经存在的文件内容嵌入到当前文件中
#include 的间接包含同样会产生嵌入文件内容的操作
test.h,test.cglobal.h
问题:
间接包含同一个头文件是否会产生编译错误?
例 3 条件编译的使用
// test.h
#ifndef _TEST_H_
#define _TEST_H_
#include "global.h"const char* NAME = "test.h";char* hello_world(){ return "Hello world!\n";}
#endif
// global.h
#ifndef _GLOBAL_H_
#define _GLOBAL_H_
int global = 10;
#endif
// #include <stdio.h>
#include "test.h"
#include "global.h"
int main()
{
const char* s = hello_world();
int g = global;
// printf("%s\n", NAME);
// printf("%d\n", g);
return 0;
}
条件编译可以解决头文件重复包含的编译错误
#ifndef _HEADER_FILE_H
#define _HEADER_FILE_H
//source code
#endif
条件编译的意义
条件编译使得我们可以按不同的条件编译不同的代码段,因而可以产生不同的目标代码
if…#else…#endif 被预编译器处理,而 if…else…语句被编译器处理,必然被编译进目标代码
实际工程中条件编译主要用于以下两种情况:
不同的产品线共用一份代码
区分编译产品的调试版和发布版
例 4 产品线区分及调试代码应用
// product.h
#define DEBUG 1
#define HIGH 1
#include <stdio.h>
#include "product.h"
#if DEBUG
#define LOG(s) printf("[%s:%d] %s\n", __FILE__, __LINE__, s)
#else
#define LOG(s) NULL
#endif
#if HIGH
void f()
{
printf("This is the high level product!\n");
}
#else
void f()
{
}
#endif
int main()
{
LOG("Enter main() ...");
f();
printf("1. Query Information.\n");
printf("2. Record Information.\n");
printf("3. Delete Information.\n");
#if HIGH
printf("4. High Level Query.\n");
printf("5. Mannul Service.\n");
printf("6. Exit.\n");
#else
printf("4. Exit.\n");
#endif
LOG("Exit main() ...");
return 0;
}
小结:
通过编译器命令能够定义预处理器使用的宏
条件编译可以避免重复包含头同一个头文件
条件编译在工程开发中可以区别不同产品线的代码条件编译可以定义产品的发布版和调试版
第23 课 #error 和#line 使用分析
#error 的用法
#error 用于生成一个编译错误消息
用法
#error message
message 不需要用双引号包围
error 编译指示字用于定义程序员特有的编译错误消息。类似的,#warning 用于生成编译警
告。
#error 是一种预编译器指示字
#error 可用于提示编译条件是否满足
#findef _cplusplus
#error this file should processed C++ compiler
#endif
编译过程中的任意错误信息意味着无法生成最终的可执行程序
例1 #error 预处理初探
#include <stdio.h>
#ifndef __cplusplus
#error This file should be processed with C++ compiler.
#endif
class CppClass
{
private:
int m_value;
public:
CppClass()
{
}
~CppClass()
{
}
};
int main()
{
return 0;
}
例2 error 在条件编译中的应用
#include <stdio.h>
void f()
{
#if ( PRODUCT == 1 )
printf("This is a low level product!\n");
#elif ( PRODUCT == 2 )
printf("This is a middle level product!\n");
#elif ( PRODUCT == 3 )
printf("This is a high level product!\n");
#endif
}
int main()
{
f();
printf("1. Query Information.\n");
printf("2. Record Information.\n");
printf("3. Delete Information.\n");
#if ( PRODUCT == 1 )
printf("4. Exit.\n");
#elif ( PRODUCT == 2 )
printf("4. High Level Query.\n");
printf("5. Exit.\n");
#elif ( PRODUCT == 3 )
printf("4. High Level Query.\n");
printf("5. Mannul Service.\n");
printf("6. Exit.\n");
#endif
return 0;
}
#include <stdio.h>
void f()
{
#if ( PRODUCT == 1 )
printf("This is a low level product!\n");
#elif ( PRODUCT == 2 )
printf("This is a middle level product!\n");
#elif ( PRODUCT == 3 )
printf("This is a high level product!\n");
#else
#error the "PRODUCT" is not defined!
#endif
}
int main()
{
f();
printf("1. Query Information.\n");
printf("2. Record Information.\n");
printf("3. Delete Information.\n");
#if ( PRODUCT == 1 )
printf("4. Exit.\n");
#elif ( PRODUCT == 2 )
printf("4. High Level Query.\n");
printf("5. Exit.\n");
#elif ( PRODUCT == 3 )
printf("4. High Level Query.\n");
printf("5. Mannul Service.\n");
printf("6. Exit.\n");
#else
#error the "PRODUCT" is not defined!
#endif
return 0;
}
#line 的用法
#line 用于强制指定新的行号和编译文件名,并对源程序的代码重新编号
用法
#line number filename
filename 可省略
#line 编译指示字的本质是重定义_LINE_和_FILE_
例3 line 的使用
#include <stdio.h>
// The code section is written by A.
// Begin
#line 1 "a.c"
// End
// The code section is written by B.
// Begin
#line 1 "b.c"
// End
// The code section is written by Delphi.
// Begin
#line 1 "delphi_tang.c"
int main()
{
printf("%s : %d\n", __FILE__, __LINE__);
printf("%s : %d\n", __FILE__, __LINE__);
return 0;
}
// End
小结:
#error 用于自定义一条编译错误信息
#warning 用于自定义一条警告信息
#error 和#warning 常用于条件编译的情形
#line 用于强制指定新的行号和编译文件名
第24 课 #pragma 使用分析
#pragma 简介
#pragma 用于指示编译器完成一些特定的工作
#pragma 所定义的很多指示字是编译器特有的
#pragma 在不同的编译器间是不可移植的
预处理将忽略它不认识的#pragma 指令
不同的编译器可能以不同的方式解释同一条#pragma一般用法:
pragma parameter
注:不同的 parameter 参数语法和意义各不相同
pragma message
message 参数在大多数的编译器中都有相似的实现 message 参数在编译时输出消息到编译输出窗口中 message 用于条件编译中可提示代码的版本信息 #if defined(ANDROID20)
pragma message(“Compile Android SDK 2.0…”)
define VERSION “Android 2.0”
endif
与#error 和#warning 不同,#pragma message 仅仅代表一条编译消息,不代表程序错误
例1 #pragma message 使用示例
#include <stdio.h>
#if defined(ANDROID20)
#pragma message("Compile Android SDK 2.0...")
#define VERSION "Android 2.0"
#elif defined(ANDROID23)
#pragma message("Compile Android SDK 2.3...")
#define VERSION "Android 2.3"
#elif defined(ANDROID40)
#pragma message("Compile Android SDK 4.0...")
#define VERSION "Android 4.0"
#else
#error Compile Version is not provided!
#endif
int main()
{
printf("%s\n", VERSION);
return 0;
}
pragma once
pragma once 用于保证头文件只被编译一次
pragma once 是编译器相关的,不一定被支持
ifndef HEADER_FILE_H
define _HEADER_FILE_H
//source code
endif
vs
pragma once
这两种方式有什么区别?
例2 pragma once 使用分析
#pragma once
int g_value = 1;
#include <stdio.h>
#include "global.h"
int main()
{
printf("g_value = %d\n", g_value);
return 0;
}
pragma pack
什么是内存对齐?
不同类型的数据在内存中按照一定的规则排列
而不一定是顺序的一个接一个的排列
struct Test1
{
char c1;
short s;
char c2;
int i;
};
struct Test2
{
char c1;
short s;
char c2;
int i;
};
Test1 和 Test2 所占的内存空间是否相同?
为什么要内存对齐?
cpu 对内存的读取不是连续的,而是分成块读取的,块的大小只能是 1、2、4、8、16.。。。
字节
当读取操作的数据未对齐,则需要两次总线周期来访问内存,因此性能会大打折扣某些硬件平台只能从规定的相对地址处读取特定类型的数据,否则产生硬件异常
pragme pack 用于指定内存对齐的方式
pragma pack 能够改变编译器的默认对齐方式
pragma pack(1)
struct Test1
{
char c1;
short s;
char c2;
int i;
};
pragma pack()
pragma pack(1)
struct Test2
{
char c1;
short s;
char c2;
int i;
};
pragma pack()
sizeof(struct Test1)=?
sizeof(struct Test2)=?
struct 占用的内存大小
第一个成员起始于 0 偏移处
每个成员按其类型大小和 pack 参数中较小的一个进行对齐偏移地址能被对齐参数整除
结构体成员的大小取其内部长度最大的数据成员作为其大小结构体总长度必须为所有对齐参数的整数倍
编译器在默认情况下按照 4 字节对齐
例3 结构体大小计算
#include <stdio.h>
#pragma pack(4)
struct Test1
{ //对齐参数 偏移地址 大小
char c1; //2 0 1
short s; //2 2 2
char c2; //1 4 1
int i; //4 8 4
};
#pragma pack()
#pragma pack(4)
struct Test2
{ //对齐参数 偏移地址 大小
char c1; //1 0 1
char c2; //1 1 1
short s; //2 2 2
int i; //4 4 4
};
#pragma pack()
int main()
{
printf("sizeof(Test1) = %d\n", sizeof(struct Test1));
printf("sizeof(Test2) = %d\n", sizeof(struct Test2));
return 0;
}
例4 结构体大小计算
#include <stdio.h>
#pragma pack(8)
struct S1
{ //对齐方式 便宜地址 大小
short a; //2 0 2
long b; //4 4 4
};
struct S2
{ //对齐方式 便宜地址 大小
char c; //1 0 1
struct S1 d; //4 4 8
double e; //8 16 8
};
#pragma pack()
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
小结:
#pragma 用于指示编译器完成一些特定的动作
#pragma 所定义的很多指示字是编译器特有的
#pragma messgae 用于自定义编译消息
#pragma once 用于保证头文件只被编译一次
#pragma pack 用于指定内存对齐方式
第 25 课 #和##操作符使用分析
运算符
运算符用于预处理期将宏参数转换为字符串
的转换作用是在预处理期完成的,因此只在宏定义中有效
编译器不知道#的转换作用
用法
define STRING(x) #x
printf(“%s\n”,STRING(Hello World!));
例1 #运算符的基本用法
#include <stdio.h>
#define STRING(x) #x
#define string(y) #y
int main()
{
printf("%s\n", STRING(Hello world!));
printf("%s\n", STRING(100));
printf("%s\n", STRING(while));
printf("%s\n", STRING(return));
printf("%s\n",string(baby));
return 0;
}
例2 #运算符的妙用
#include <stdio.h>
#define CALL(f, p) (printf("Call function %s\n", #f), f(p))
int square(int n)
{
return n * n;
}
int func(int x)
{
return x;
}
int main()
{
int result = 0;
result = CALL(square, 4);
printf("result = %d\n", result);
result = CALL(func, 10);
printf("result = %d\n", result);
return 0;
}
## 运算符
## 运算符用于在预处理粘连两个标识符
## 的连接作用是在预处理期完成的,因此只在宏定义中有效
编译器不知道##的连接作用
用法
#define CONNECT(a,b) a##b
int CONNECT(a,1); //int a1;
a1=2;
例3 ##运算符的基本用法
#include <stdio.h>
#define NAME(n) name##n
int main()
{
int NAME(1); //name1
int NAME(2); //name2
NAME(1) = 1; //name1=1
NAME(2) = 2; //name2=2
printf("%d\n", NAME(1));
printf("%d\n", NAME(2));
printf("%d\n",name1);
return 0;
}
例4 ##运算符的工程运用
#include <stdio.h>
#define STRUCT(type) typedef struct _tag_##type type;\
struct _tag_##type
STRUCT(Student)
{
char* name;
int id;
};
int main()
{
Student s1;
Student s2;
s1.name = "s1";
s1.id = 0;
s2.name = "s2";
s2.id = 1;
printf("s1.name = %s\n", s1.name);
printf("s1.id = %d\n", s1.id);
printf("s2.name = %s\n", s2.name);
printf("s2.id = %d\n", s2.id);
return 0;
}
小结:
运算符用于预处理期将宏参数转换为字符串
运算符用于在预处理期粘连两个标识符
编译器不知道#和##运算符的存在
和##运算符只在宏定义中有效
第 26 课 指针的本质分析
变量回顾
程序中的变量只是一段存储空间的别名,那么是不是必须通过这个别名才能使用这段存储空
间
下面程序输出什么?为什么?
int i=5;
int *p=&i;
printf(“%d,%p\n”,i,p);
*p=10;
printf(“%d,%p\n”,i,p);
*号的意义
在指针声明时,*号表示所声明的变量为指针
在指针使用时,*号表示取指针所向的内存空间中的值
int i=0;
int j=0;
//指针声明
int *p=&i;
//取值
j=*p;
*号类似一把钥匙,通过这把钥匙可以打开内存,读取内存中的值
*号的意义
小贴士
int i=0;
int j=0;
int *p=&i;
j=*p;
↓
变量 p 保存着变量 i 的内存地址,即:
p&i
*pi
例 1 指针使用示例
#include <stdio.h>
int main()
{
int i = 0;
int* pI;
char* pC;
float* pF;
pI = &i;
*pI = 10;
printf("%p, %p, %d\n", pI, &i, i);
printf("%d, %d, %p\n", sizeof(int*), sizeof(pI), &pI);
printf("%d, %d, %p\n", sizeof(char*), sizeof(pC), &pC);
printf("%d, %d, %p\n", sizeof(float*), sizeof(pF), &pF);
return 0;
}
传值调用与传址调用
指针是变量,因此可以声明指针参数
当一个函数体内部需要改变实参的值,则需要使用指针参数函数调用时实参值将复制到形参
指针适用于复杂数据类型作为参数的函数中
例 2 利用指针交换变量
#include <stdio.h>
int swap(int* a, int* b)
{
int c = *a;
*a = *b;
*b = c;
}
int main()
{
int aa = 1;
int bb = 2;
printf("aa = %d, bb = %d\n", aa, bb);
swap(&aa, &bb);
printf("aa = %d, bb = %d\n", aa, bb);
return 0;
}
常量与指针
const int *p; //p 可变,p 指向的内容不可变
int const *p; //p 可变,p 指向的内容不可变
int *const p; //不可变,p 指向的内容可变
const int * const p; //p 和 p 指向的内容不可变
口诀:左数右指
当const 出现在*号左边时,指针指向的数据为常量
当const 出现在*后右边时,指针本身为常量
例 3 常量与指针
#include <stdio.h>
int main()
{
int i = 0;
const int* p1 = &i;
int const* p2 = &i;
int* const p3 = &i;
const int* const p4 = &i;
*p1 = 1; // compile error
p1 = NULL; // ok
*p2 = 2; // compile error
p2 = NULL; // ok
*p3 = 3; // ok
p3 = NULL; // compile error
*p4 = 4; // compile error
p4 = NULL; // compile error
return 0;
}
小结:
指针是 c 语言中一种特别的变量
指针所保存的值是内存的地址
可以通过指针修改内存中的任意地址内容
第 27 课 数组的本质分析
数组的概念
数组是相同类型的变量的有序集合
数组示意图:数组包含 5 个 int 类型的数据
int a[5];
a代表数组第一个元素的起始地址
这20 个字节空间的名字为 a,a[0],a[1]等都是 a 中的元素,并非元素的名字。数组中的元素没有名字
5 个 int 类型的数据,每个元素都是 int 类型的
数组的大小
数组在一片连续的内存空间中存储元素
数组元素的个数可以显示或隐式指定
int a[5]={1,2};
int b[]={1,2};
问题:
1、a[2],a[]3,a[4]的值是多少?
2、b 包含了多少个元素?
例 1 数组的初始化
#include <stdio.h>
int main()
{
int a[5] = {1, 2};
int b[] = {1, 2};
printf("a[2] = %d\n", a[2]);
printf("a[3] = %d\n", a[3]);
printf("a[4] = %d\n", a[4]);
printf("sizeof(a) = %d\n", sizeof(a));
printf("sizeof(b) = %d\n", sizeof(b));
printf("count for a: %d\n", sizeof(a)/sizeof(int));
printf("count for b: %d\n", sizeof(b)/sizeof(int));
return 0;
}
数组地址与数组名
数组名代表数组首元素的地址
数组地址需要用取取地址符&才能得到
数组首元素的地址值与数组的地址值相同
数组首元素的地址与数组的地址是两个不同的概念
例 2 数组名和数组地址
#include <stdio.h>
int main()
{
int a[5] = { 0 };
printf("a = %p\n", a);
printf("&a = %p\n", &a);
printf("&a[0] = %p\n", &a[0]);
return 0;
}
数组名的盲点:
数组名可以看作一个常量指针
数组名“指向”的是内存中数组首元素的起始地址数组名不包含数组的长度信息
在表达式中数组名只能作为右值使用
只有在下列场合数组名不能看做常量指针
数组名作为 sizeof 操作符的参数
数组名作为&运算符的参数
例 3 数组和指针并不相同
#include <stdio.h>
int main()
{
int a[5] = {0};
int b[2];
int* p = NULL;
p = a;
printf("a = %p\n", a);
printf("p = %p\n", p);
printf("&p = %p\n", &p);
printf("sizeof(a) = %d\n", sizeof(a));
printf("sizeof(p) = %d\n", sizeof(p));
printf("\n");
p = b;
printf("b = %p\n", b);
printf("p = %p\n", p);
printf("&p = %p\n", &p);
printf("sizeof(b) = %d\n", sizeof(b));
printf("sizeof(p) = %d\n", sizeof(p));
b = a;
return 0;
}
小结:
数组是一片连续的内存空间
数组的地址和数组首元素的地址意义不同
数组名在大多数情况下被当成常量指针处理
数组名其实并不是指针,不能将其等同于指针
第 28 课 指针和数组分析(上)
数组的本质
数组是一段连续的内存空间
数组的空间大小为 sizeof(array_type) *array_size
数组名可看做指向数组第一个元素的常量指针
问题:
1、a+1 的意义是什么?结果是什么?
2、指针运算的意义是什么?结果又是什么?
例 1 a+1 的结果是什么
#include <stdio.h>
int main()
{
int a[5] = {0};
int* p = NULL;
printf("a = 0x%X\n", (unsigned int)(a));
printf("a + 1 = 0x%X\n", (unsigned int)(a + 1));
printf("p = 0x%X\n", (unsigned int)(p));
printf("p + 1 = 0x%X\n", (unsigned int)(p + 1));
return 0;
}
指针的运算
指针是一种特殊的变量,与整数的运算规则为
p+n;(unsigned int)p+n*sizeof(*p);
结论:
当指针指向一个同类型的数组的元素时:p+1 将指向当前元素的下一个元素;p-1 将指向当前元素的上一个元素。
指针之间只支持减法运算
参数减法运算的指针类型必须相同
p1-p2;((unsigned int)p1-(unsigned int)p2)/sizeof(type); 注意:
1、只有当两个指针指向同一个数组中的元素时,指针相减才有意义,其意义为指针所指元素的下标差
2、当两个指针指向的元素不在同一个数组中时,结果未定义
指针的比较
指针也可以进行关系运算(<,<=,>,>=)
指针关系运算的前提是同时指向同一个数组中的元素
任意两个指针之间的比较运算(==,!=)无限制
参与比较运算的指针类型必须相同
int *p1;
char *p2;
p1
#include <stdio.h>
int main()
{
char s1[] = {'H', 'e', 'l', 'l', 'o'};
int i = 0;
char s2[] = {'W', 'o', 'r', 'l', 'd'};
char* p0 = s1;
char* p1 = &s1[3];
char* p2 = s2;
int* p = &i;
printf("%d\n", p0 - p1); //ok -3
//printf("%d\n", p0 + p2); //error
//printf("%d\n", p0 - p2); //error
//printf("%d\n", p0 - p); //error
//printf("%d\n", p0 * p2); //error
//printf("%d\n", p0 / p2); //error
return 0;
}
例3 指针运算的应用
#include <stdio.h>
#define DIM(a) (sizeof(a) / sizeof(*a))
int main()
{
char s[] = {'H', 'e', 'l', 'l', 'o'};
char* pBegin = s;
char* pEnd = s + DIM(s); // Key point
char* p = NULL;
printf("pBegin = %p\n", pBegin);
printf("pEnd = %p\n", pEnd);
printf("Size: %d\n", pEnd - pBegin);
for(p=pBegin; p<pEnd; p++)
{
printf("%c", *p);
}
printf("\n");
return 0;
}
小结:
数组声明时编译器自动分配一片连续的内存空间指针声明时只分配了用于容纳地址值的 4 字节空间指针和整数可以进行运算,其结果为指针
指针之间只支持减法运算,其结果为数组元素下标差指针之间支持比较运算,其类型必须相同
第 29 课 指针和数组分析(下)
问题:
数组名可以当作常量指针使用,那么指针是否也可以当作数组名来使用呢?
以下标的形式访问数组中的元素
int main()
{
int a[5]={0};
a[1]=3;
a[2]=5;
return 0;
}
以指针的形式访问数组中的元素
int main()
{
int a[5]={0};
*(a+1)=3;
*(a+2)=5;
return 0;
}
下标形式 VS 指针形式
指针以固定增量在数组中移动时,效率高于下标形式
指针增量为 1 且具有硬件增量模型时,效率更高
下标形式与指针形式的转换
a[n](a+n)(n+a)n[a]
注意:
现代编译器的生成代码优化率大大提高,在固定增量时,下标形式的效率已经和指针形式相当;但从可读性和代码维护的角度来看,下标形式更优。
例1 数组的访问方式
#include <stdio.h>
int main()
{
int a[5] = {0};
int* p = a;
int i = 0;
for(i=0; i<5; i++)
{
p[i] = i + 1;
}
for(i=0; i<5; i++)
{
printf("a[%d] = %d\n", i, *(a + i));
}
printf("\n");
for(i=0; i<5; i++)
{
i[a] = i + 10;
}
for(i=0; i<5; i++)
{
printf("p[%d] = %d\n", i, p[i]);
}
return 0;
}
例2 数组和指针的不同
int a[] = {1, 2, 3, 4, 5};
#include <stdio.h>
int main()
{
extern int* a;
printf("&a = %p\n", &a);
printf("a = %p\n", a);
printf("*a = %d\n", *a);
return 0;
}
a 和&a 的区别
a为数组首元素的地址
&a 为整个数组的地址
a 和&a 的区别在于指针运算
a+1(unsigned int)a+sizeof(*a)
&a+1(unsigned int)(&a)+sizeof(*&a)
(unsigned int)(&a)+sizeof(a)
例 3 指针运算经典问题
#include <stdio.h>
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int* p1 = (int*)(&a + 1);
int* p2 = (int*)((int)a + 1);
int* p3 = (int*)(a + 1);
printf("%d, %d, %d\n", p1[-1], p2[0], p3[1]);
return 0;
}
// A. 数组下标不能是负数,程序无法运行
// B. p1[-1]将输出随机数,p2[0]输出2, p3[1]输出3
// C. p1[-1]将输出乱码, p2[0]和p3[1]输出2
数组参数
数组作为函数参数时,编译器将其编译成为对应的指针
void f(int a[]);void f(int *a);
void f(int a[5]);void f(int *a);
结论:
一般情况下,当定义的函数中有数组参数时,需要定义另一个参数来标示数组的大小。
例 4 虚幻的数组参数
#include <stdio.h>
void func1(char a[5])
{
printf("In func1: sizeof(a) = %d\n", sizeof(a));
*a = 'a';
a = NULL;
}
void func2(char b[])
{
printf("In func2: sizeof(b) = %d\n", sizeof(b));
*b = 'b';
b = NULL;
}
int main()
{
char array[10] = {0};
func1(array);
printf("array[0] = %c\n", array[0]);
func2(array);
printf("array[0] = %c\n", array[0]);
return 0;
}
小结:
数组名和指针仅使用方式相同
数组名的本质不是指针
指针的本质不是数组
数组名并不是数组的地址,而是数组首元素的地址函数的数组参数退化为指针
第 30 课 c 语言中的字符串
字符串的概念
字符串是有序字符的集合
字符串是程序中的基本元素之一
c语言中没有字符串的概念
c语言中通过特殊的字符数组模拟字符串
c语言中的字符串是以‘\0’结尾的字符数组
字符数组与字符串
在c 语言中,双引号引用的单个或多个字符是一种特殊的字面量存储于程序的全局只读存储区
本质为字符数组,编译器自动在结尾加上‘\0’字符
下面那些是字符串的定义?
char ca[]={‘H’,e’,’l’,’o’};
char sa[]={‘W’,’o’,’r’,’l’,’d’,’\0’};
char ss[]=”hello world”;
char *str=”hello world”;
例 1 字符数组与字符串
#include <stdio.h>
int main()
{
char ca[] = {'H', 'e', 'l', 'l', 'o'};
char sa[] = {'W', 'o', 'r', 'l', 'd', '\0'};
char ss[] = "Hello world!";
char* str = "Hello world!";
printf("%s\n", ca);
printf("%s\n", sa);
printf("%s\n", ss);
printf("%s\n", str);
return 0;
}
鲜为人知的小秘密
你知道吗?
字符串字面量的本质是一个数组
字符串字面量可以看作常量指针
字符串字面量中的字符不可改变
字符串字面量至少包含一个字符
字符串字面量
“hello world!”是一个无名的字符数组
下面的表达式正确吗?
char b=”abc”[0];
char c=*(“123”+1);
char t=*””;
例 2 字符串字面量的本质
#include <stdio.h>
int main()
{
char b = "abc"[0];
char c = *("123" + 1);
char t = *"";
printf("%c\n", b);
printf("%c\n", c);
printf("%d\n", t);
printf("%s\n", "Hello");
printf("%p\n", "World");
return 0;
}
字符串的长度
字符串的长度就是字符串所包含字符的个数
字符串长度指的是第一个‘\0’字符前出现的字符个数
通过‘\0’结束符确定字符串的长度
函数 strlen 用于返回字符串的长度
char s[]=”Hello”;
printf(“%d\n”,strlen(s));
printf(“%d\n”,strlen(“123”));
例3 strlen 的使用
#include <stdio.h>
#include <string.h>
int main()
{
char s[] = "Hello\0world";
int i = 0;
for(i=0; i<sizeof(s)/sizeof(char); i++)
{
printf("%c\n", s[i]);
}
printf("%s\n", s);
printf( "%d\n", strlen(s) );
printf( "%d\n", strlen("123") );
return 0;
}
小结:
c语言中通过字符数组模拟字符串
c语言中的字符串使用‘\0’作为结束符字符串字面量的本质为字符数组
字符串相关函数都依赖于结束符
第31 课 字符串典型问题分析
典型问题一
下面的程序输出什么为什么?
char buf[10]={0};
char src[]=”hello %s”;
snprintf(buf,sizeof(buf),src);
printf(“buf=%s\n”,buf);
例1 snprintf
#include <stdio.h>
int main()
{
char buf[10] = {0};
char src[] = "hello %s";
snprintf(buf, sizeof(buf), src);
printf("buf = %s\n", buf);
return 0;
}
分析
snprintf 函数本身是可变参数函数,原型如下:
int snprintf(char *buffer,int buf_size,const char *fomart,…)
当函数只有 3 个参数时,如果第三个参数没有包含格式化信息,函数调用没有问题;相
反,如果第三个参数包含格式化信息,但缺少后续对应参数,则程序行为不确定。
典型问题二
下面的程序输出什么为什么?
#define STR “Hello,\baby\0”
char *src=STR;
char buf[255]={0};
snprintf(buf,sizeof(buf),src);
printf(“strlen(STR)=%d\n”,strlen(STR));
printf(“sizeof(STR)=%d\n”,sizeof(STR));
printf(“src=%s\n”,src);
printf(“buf=%s\n”,buf);
例2 snprintf
#include <stdio.h>
#include <string.h>
int main()
{
#define STR "Hello, \0D.T.Software\0"
char* src = STR;
char buf[255] = {0};
snprintf(buf, sizeof(buf), src);
printf("strlen(STR) = %d\n", strlen(STR)); //7
printf("sizeof(STR) = %d\n", sizeof(STR)); //22
printf("strlen(src) = %d\n", strlen(src)); //7
printf("sizeof(src) = %d\n", sizeof(src)); //4
printf("strlen(buf) = %d\n", strlen(buf)); //7
printf("sizeof(buf) = %d\n", sizeof(buf)); //255
printf("src = %s\n", src); //helloi,
printf("buf = %s\n", buf); //hello,
return 0;
}
分析
字符串相关的函数均以第一个出现的‘\0’作为结束符编译器总是会在字符串字面量的末尾添加‘\0’字符串字面量的本质为数组
字符串,字符数组,字符指针是不同的
典型问题三
下面的程序输出什么为什么?
#define S1 “baby”
#define S2 “baby”
if(S==S2)
{
printf(“Equal\n”);
}
else
{
printf(“Non Equal\n”);
}
例3 strcmp
#include <stdio.h>
#include <string.h>
int main()
{
#define S1 "D.T.Software"
#define S2 "D.T.Software"
if( S1 == S2 )
{
printf("Equal\n");
}
else
{
printf("Non Equal\n");
}
if( strcmp(S1, S2) == 0 )
{
printf("Equal\n");
}
else
{
printf("Non Equal\n");
}
return 0;
}
分析
字符串之间的相等比较需要用 strcmp 完成
不可直接用==进行字符串直接的比较
完全相同的字符串字面量的==比较结果为 false
一些现代编译器能够将相同的字符串字面量映射到同一个无名字符数组,因此==比较结果为
true。
不编写特殊编译器的代码
典型问题四
字符串循环右移
void right_shift_r(const char *src,char *result,unsigned int n);
函数功能:
将输入字符串 src 循环右移 n 位,result 为输出结果要求:
以效率最高的方式实现
示例:
“abcde” –2 “deabc”
“abcde” –8 “cdeab”
例4 move
#include <stdio.h>
#include <string.h>
void right_shift_r(const char* src, char* result, unsigned int n)
{
const unsigned int LEN = strlen(src);
int i = 0;
for(i=0; i < LEN; i++)
{
result[(n + i) % LEN] = src[i];
}
result[LEN] = '\0';
}
int main()
{
char result[255] = {0};
right_shift_r("abcde", result, 2);
printf("%s\n", result);
right_shift_r("abcde", result, 5);
printf("%s\n", result);
right_shift_r("abcde", result, 8);
printf("%s\n", result);
return 0;
}
第 32 课 数组指针和指针数组分析
思考
下面这些声明合法吗?
int array[5];
int matrix[3][3];
int *pa=array;
int *pm=matrix;
问题:
array 代表数组首元素的地址,那么 matrix 代表什么?
array 和&array 的地址值相同,但是意义不同,那么它们所代表的类型相同吗?
数组类型
c语言中的数组有自己特定的类型
数组的类型由元素类型和数组大小共同决定例:int array[5]的类型为 int[5]
工作中的对话。。。。X
A:这里定义的数组什么类型的?
B:int 型
定义数组类型
c 语言中通过 typedef 为数组类型重命名
typedef type(name)[size];
数组类型:
typedef int(AINT5)[5];
typedef float(AFLOAT10)[10];
数组定义:
AINT5 iArray;
AFLOAT10 fArray;
数组指针
数组指针用于指向一个数组
数组名是数组首元素的起始地址,但并不是数组的起始地址
通过将取地址&作用于数组名可以得到数组的起始地址
可通过数组类型定义数组指针:ArrayType *pointer;
也可以直接定义:type(*pointer)[n];
pointer 为数组指针变量名
type 为指向的数组的元素类型
n为指向的数组的大小
例1 数组类型和数组指针
#include <stdio.h>
typedef int(AINT5)[5];
typedef float(AFLOAT10)[10];
typedef char(ACHAR9)[9];
int main()
{
AINT5 a1;
float fArray[10];
AFLOAT10* pf = &fArray;
ACHAR9 cArray;
char(*pc)[9] = &cArray;
char(*pcw)[4] = cArray;
int i = 0;
printf("%d, %d\n", sizeof(AINT5), sizeof(a1));
for(i=0; i<10; i++)
{
(*pf)[i] = i;
}
for(i=0; i<10; i++)
{
printf("%f\n", fArray[i]);
}
printf("%p, %p, %p\n", &cArray, pc+1, pcw+1);
return 0;
}
指针数组
指针数组是一个普通的数组
指针数组中每个元素为一个指针
指针数组的定义:type *pArray[n];
type*为数组中每个元素的类型
pArray 为数组名
n为数组大小
例2 指针数组的应用
#include <stdio.h>
#include <string.h>
#define DIM(a) (sizeof(a)/sizeof(*a))
int lookup_keyword(const char* key, const char* table[], const int size)
{
int ret = -1;
int i = 0;
for(i=0; i<size; i++)
{
if( strcmp(key, table[i]) == 0 )
{
ret = i;
break;
}
}
return ret;
}
int main()
{
const char* keyword[] = {
"do",
"for",
"if",
"register",
"return",
"switch",
"while",
"case",
"static"
};
printf("%d\n", lookup_keyword("return", keyword, DIM(keyword)));
printf("%d\n", lookup_keyword("main", keyword, DIM(keyword)));
return 0;
}
小结:
数组的类型由元素类型和数组大小共同决定
数组指针是一个指针,指向对应类型的数组
指针数组是一个数组,其中每个元素都为指针
数组指针遵循指针运算法则
指针数组拥有 c 语言数组的各种特性
第 33 课 main 函数与命令行参数
main 函数的概念
c 语言中 main 函数称之为主函数
一个 c 程序是从 main 函数开始执行的
下面的 main 函数定义正确吗?
A、main(){}
B、void main(){}
C、int main(){}
D、int main(){return 0;}
例 1 main 函数的原型究竟是什么
int main()
{
return 0;
}
main 函数的本质
main 函数是操作系统调用的函数
操作系统总是将 main 函数作为应用程序的开始
操作系统将 main 函数的返回值作为程序的退出状态思考:
为什么 c 编译器支持那么多不同的 main 函数原型
例 2 main 函数的返回值
#include <stdio.h>
int main()
{
printf("I'm A!\n");
return 0;
}
#include <stdio.h>
int main()
{
printf("I'm B!\n");
return 99;
}
程序执行时可以向 main 函数传递参数
int main()
int main(int argc)
int main(int argc,char *argv[])
int main(int argc,char *argv[],char *env[])
argc:命令行参数个数
argv:命令行参数数组
env:环境变量数组
main 函数的参数
gcc a.c b.c c.c
argc 4
argv[0] gcc
argv[1] a.c
argv[2] b.c
argv[3] c.c
例3 main 函数的参数
#include <stdio.h>
int main(int argc, char* argv[], char* env[])
{
int i = 0;
printf("============== Begin argv ==============\n");
for(i=0; i<argc; i++)
{
printf("%s\n", argv[i]);
}
printf("============== End argv ==============\n");
printf("\n");
printf("\n");
printf("\n");
printf("============== Begin env ==============\n");
for(i=0; env[i]!=NULL; i++)
{
printf("%s\n", env[i]);
}
printf("============== End env ==============\n");
return 0;
}
小技巧
面试中的小问题
main 函数一定是程序执行的第一个函数吗?
例 4 gcc 中的属性关键字
#include <stdio.h>
#ifndef __GNUC__
#define __attribute__(x)
#endif
__attribute__((constructor))
void before_main()
{
printf("%s\n",__FUNCTION__);
}
__attribute__((destructor))
void after_main()
{
printf("%s\n",__FUNCTION__);
}
int main()
{
printf("%s\n",__FUNCTION__);
return 0;
}
小结:
一个 c 程序是从 main 函数开始执行的
main 函数是操作系统调用的函数
main 函数有参数和返回值
现代编译器支持在 main 函数前调用其他函数
第 34 课 多维数组和多维指针
指向指针的指针
指针的本质是变量
指针会占用一定的内存空间
可以定义指针的指针来保存指针变量的地址值
int main()
{
int i=0;
int *p=NULL;
int **pp=NULL;
pp=&p;
*pp=&i;
return 0;
}
问题
为什么需要指向指针的指针?
指针在本质上也是变量
对于指针也同样存在传值调用与传址调用
例 1 重置动态空间的大小
#include <stdio.h>
#include <malloc.h>
int reset(char**p, int size, int new_size)
{
int ret = 1;
int i = 0;
int len = 0;
char* pt = NULL;
char* tmp = NULL;
char* pp = *p;
if( (p != NULL) && (new_size > 0) )
{
pt = (char*)malloc(new_size);
tmp = pt;
len = (size < new_size) ? size : new_size;
for(i=0; i<len; i++)
{
*tmp++ = *pp++;
}
free(*p);
*p = pt;
}
else
{
ret = 0;
}
return ret;
}
int main()
{
char* p = (char*)malloc(5);
printf("%p\n", p);
if( reset(&p, 5, 3) )
{
printf("%p\n", p);
}
free(p);
return 0;
}
二维数组与二级指针
二维数组在内存中以一维的方式排布
二维数组中的第一维是一维数组
二维数组中的第二维才是具体的值
二维数组的数组名可看做常量指针
例 2 遍历二维数组
#include <stdio.h>
#include <malloc.h>
void printArray(int a[], int size)
{
int i = 0;
printf("printArray: %d\n", sizeof(a));
for(i=0; i<size; i++)
{
printf("%d\n", a[i]);
}
}
int main()
{
int a[3][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}};
int* p = &a[0][0];
int i = 0;
int j = 0;
for(i=0; i<3; i++)
{
for(j=0; j<3; j++)
{
printf("%d, ", *(*(a+i) + j)); //*(a+i)==>a[i]
} //*(a[i]+j)==>a[i][j]
printf("\n");
}
printf("\n");
printArray(p, 9);
return 0;
}
数组名
一维数组名代表数组首元素的地址
int a[5] a 的类型为 int*
二维数组名同样代表数组首元素的地址
int m[2][5] m 的类型为 int(*)[5]
结论:
1、二维数组名可以看作是指向数组的常量指针
2、二维数组可以看作是一维数组
3、二维数组中的每个元素都是同类型的一维数组
例 3 如何动态申请二维数组
#include <stdio.h>
#include <malloc.h>
int** malloc2d(int row, int col)
{
int** ret = NULL;
if( (row > 0) && (col > 0) )
{
int* p = NULL;
ret = (int**)malloc(row * sizeof(int*));
p = (int*)malloc(row * col * sizeof(int));
if( (ret != NULL) && (p != NULL) )
{
int i = 0;
for(i=0; i<row; i++)
{
ret[i] = p + i * col;
}
}
else
{
free(ret);
free(p);
ret = NULL;
}
}
return ret;
}
void free2d(int** p)
{
if( *p != NULL )
{
free(*p);
}
free(p);
}
int main()
{
int** a = malloc2d(3, 3);
int i = 0;
int j = 0;
for(i=0; i<3; i++)
{
for(j=0; j<3; j++)
{
printf("%d, ", a[i][j]);
}
printf("\n");
}
free2d(a);
return 0;
}
小结:
c语言中只支持一维数组
c语言中的数组大小必须在编译器就作为常数确定
c语言中的数组元素可是任何类型的数据
c语言中的数组的元素可以是另一个数组
第35 课 数组参数和指针参数分析
思考
为什么 c 语言中的数组参数会退化为指针
退化的意义
c语言中只会以值拷贝的方式传递参数当向函数传递数组时:
将整个数组拷贝一份传入函数 X
将数组名看作常量指针传数组首元素地址
c语言以高效为最初设计目标
a)参数传递的时候如果拷贝整个数组执行效率将大大下降b)参数位于栈上,太大的数组拷贝将导致栈溢出
二维数组参数
二维数组参数同样存在退化的问题
二维数组可以看作是一维数组
二维数组中的每个元素是一维数组
二维数组参数中第一维的参数可以省略
void f(int a[5])void f(int a[])void f(int *a)
void g(int a[3][3])void g(int a[][3])void g(int (*a)[])
等价关系
数组参数 等效的指针参数
一维数组:float a[5] 指针:float *a
指针数组:int *a[5] 指针的指针 int **a
二维数组:char a[3][4] 数组的指针:char (*a)[4]
被忽视的知识点
c语言中无法向一个函数传递任意的多维数组必须提供除第一维之外的所有维长度
第一维之外的维度信息用于完成指针运算
N 维数组的本质是一维数组,元素是 N-1 维数组对于多维数组的函数参数只有第一维是可变的
例 1 传递与访问二维数组
#include <stdio.h>
void access(int a[][3], int row)
{
int col = sizeof(*a) / sizeof(int);
int i = 0;
int j = 0;
printf("sizeof(a) = %d\n", sizeof(a));
printf("sizeof(*a) = %d\n", sizeof(*a));
for(i=0; i<row; i++)
{
for(j=0; j<col; j++)
{
printf("%d\n", a[i][j]);
}
}
printf("\n");
}
void access_ex(int b[][2][3], int n)
{
int i = 0;
int j = 0;
int k = 0;
printf("sizeof(b) = %d\n", sizeof(b));
printf("sizeof(*b) = %d\n", sizeof(*b)); //int[2][3]=24
for(i=0; i<n; i++)
{
for(j=0; j<2; j++)
{
for(k=0; k<3; k++)
{
printf("%d\n", b[i][j][k]);
}
}
}
printf("\n");
}
int main()
{
int a[3][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}};
int aa[2][2] = {0};
int b[1][2][3] = {0};
//access(a, 3);
//access(aa, 2);
access_ex(b, 1);
//access_ex(aa, 2);
return 0;
}
小结:
c语言中只会以值拷贝的方式传递参数
c语言中的数组参数必然退化为指针
多维数组参数必须提供第一维之外的所有维长度对于多维数组的函数参数只有第一维是可变的
第36 课 函数与指针分析
函数类型
c语言中的函数有自己特定的类型
函数的类型由返回值,参数类型和参数个数共同决定
int add(int i,int j)的类型为 int(int,int) c 语言中通过 typedef 为函数类型重命名
typedef type name(parameter list)
例:
typedef int f(int,int);
typedef void p(int);
函数指针用于指向一个函数
函数名是执行函数体的入口地址
可通过函数类型定义函数指针:FuncType *pointer;
也可以直接定义:type (*pointer)(parameter list);
pointer 为函数指针变量名
type 为所指函数的返回值类型
parameter list 为所指函数的参数类型列表
面试小问题
如何使用 c 语言跳转到某个固定的地址开始执行?
例 1 函数指针的使用
#include <stdio.h>
typedef int(FUNC)(int);
int test(int i)
{
return i * i;
}
void f()
{
printf("Call f()...\n");
}
int main()
{
FUNC* pt = test;
void(*pf)() = &f;
printf("pf = %p\n", pf);
printf("f = %p\n", f);
printf("&f = %p\n", &f);
pf();
(*pf)(); //pf()
printf("Function pointer call: %d\n", pt(2)); //4
return 0;
}
回调函数
回调函数时利用函数指针实现的一种调用机制
回调机制原理
调用者不知道具体时间发生时需要调用的具体函数被调函数不知道何时被调用,只知道需要完成的任务当具体时间发生时,调用者通过函数指针调用具体函数
回调机制中的调用者和被调函互不依赖
例 2 回调函数使用示例
#include <stdio.h>
typedef int(*Weapon)(int);
void fight(Weapon wp, int arg)
{
int result = 0;
printf("Fight boss!\n");
result = wp(arg);
printf("Boss loss: %d\n", result);
}
int knife(int n)
{
int ret = 0;
int i = 0;
for(i=0; i<n; i++)
{
printf("Knife attack: %d\n", 1);
ret++;
}
return ret;
}
int sword(int n)
{
int ret = 0;
int i = 0;
for(i=0; i<n; i++)
{
printf("Sword attack: %d\n", 5);
ret += 5;
}
return ret;
}
int gun(int n)
{
int ret = 0;
int i = 0;
for(i=0; i<n; i++)
{
printf("Gun attack: %d\n", 10);
ret += 10;
}
return ret;
}
int main()
{
fight(knife, 3);
fight(sword, 4);
fight(gun, 5);
return 0;
}
小结:
c语言中的函数都有特定的类型可以使用函数类型定义函数指针
函数指针是实现回调机制的关键技术
通过函数指针可以在 c 程序中实现固定地址跳转
第37 课 指针阅读技巧分析
笔试中的问题
int (p1)(int ,int(f)(int ));
int (p2[5])(int );
int ((*p3)[5])(int );
int ((p4)(int ))(int *);
int ((*p5)(int ))[5];
指针阅读技巧解析
右左法则
从最里层的圆括号中未定义的标识符看起
首先往右看,再往左看
遇到圆括号或方括号时可以确定部分类型,并调转方向重复 2,3 步骤,知道阅读结束
例 1 复杂指针的阅读
#include <stdio.h>
int main()
{
int (*p1)(int*, int (*f)(int*));
int (*p2[5])(int*);
int (*(*p3)[5])(int*);
int*(*(*p4)(int*))(int*);
int (*(*p5)(int*))[5];
return 0;
}
小结:
右左法则总结于编译器对指针变量的解析过程
指针阅读练习的意义在于理解指针的组合定义
可通过 typedef 简化复杂指针的定义
第 38 课 动态内存分配
动态内存分配的意义
c语言中的一切操作都是基于内存的变量和数组都是内存的别名
内存分配由编译器在编译期间决定定义数组的时候必须指定数组长度数组长度是在编译期就必须确定的
需求:程序运行的过程中,可能需要使用一些额外的内存空间
malloc 和 free
malloc 和 free 用于执行动态内存分配和释放
malloc 所分配的是一块连续的内存
malloc 以字节为单位,并且不带任何的类型信息
free 用于将动态内存归还系统
void *malloc(size_t size);
void free(void *pointer);
注意:
malloc 和 free 是库函数,而不是系统调用
malloc 实际分配的内存可能会比请求的多
不能依赖于不同平台下的 malloc 行为
当请求的动态内存无法满足时,malloc 返回 NULL
当 free 的参数为 NULL,函数直接返回
思考:
malloc(0);将返回什么?
例1 malloc(0)
#include <stdio.h>
int main(void)
{
int *p=(int *)malloc(0);
printf("p=%p\n",p);
free(p);
return 0;
}
例2 内存泄漏检测模块
#include <stdio.h>
#include "mleak.h"
void f()
{
MALLOC(100);
}
int main()
{
int* p = (int*)MALLOC(3 * sizeof(int));
f();
p[0] = 1;
p[1] = 2;
p[2] = 3;
FREE(p);
PRINT_LEAK_INFO();
return 0;
}
#include "mleak.h"
#define SIZE 256
/* 动态内存申请参数结构体 */
typedef struct
{
void* pointer;
int size;
const char* file;
int line;
} MItem;
static MItem g_record[SIZE]; /* 记录动态内存申请的操作 */
void* mallocEx(size_t n, const char* file, const line)
{
void* ret = malloc(n); /* 动态内存申请 */
if( ret != NULL )
{
int i = 0;
/* 遍历全局数组,记录此次操作 */
for(i=0; i<SIZE; i++)
{
/* 查找位置 */
if( g_record[i].pointer == NULL )
{
g_record[i].pointer = ret;
g_record[i].size = n;
g_record[i].file = file;
g_record[i].line = line;
break;
}
}
}
return ret;
}
void freeEx(void* p)
{
if( p != NULL )
{
int i = 0;
/* 遍历全局数组,释放内存空间,并清除操作记录 */
for(i=0; i<SIZE; i++)
{
if( g_record[i].pointer == p )
{
g_record[i].pointer = NULL;
g_record[i].size = 0;
g_record[i].file = NULL;
g_record[i].line = 0;
free(p);
break;
}
}
}
}
void PRINT_LEAK_INFO()
{
int i = 0;
printf("Potential Memory Leak Info:\n");
/* 遍历全局数组,打印未释放的空间记录 */
for(i=0; i<SIZE; i++)
{
if( g_record[i].pointer != NULL )
{
printf("Address: %p, size:%d, Location: %s:%d\n", g_record[i].pointer, g_record[i].size, g_record[i].file, g_record[i].line);
}
}
}
#ifndef _MLEAK_H_
#define _MLEAK_H_
#include <malloc.h>
#define MALLOC(n) mallocEx(n, __FILE__, __LINE__)
#define FREE(p) freeEx(p)
void* mallocEx(size_t n, const char* file, const line);
void freeEx(void* p);
void PRINT_LEAK_INFO();
#endif
calloc 和 realloc
malloc 的同胞兄弟
void *calloc(size_t num,size_t size);
void *realloc(void *pointer,size_t new_size);
calloc 的参数将返回的内存初始化为 0
realloc 用于修改一个原先已经分配的内存块大小
在使用 realloc 之后应该使用其返回值
当pointer 的第一个参数为 NULL,等价于 malloc
例3 calloc 和 realloc 的使用
#include <stdio.h>
#include <malloc.h>
#define SIZE 5
int main()
{
int i = 0;
int* pI = (int*)malloc(SIZE * sizeof(int));
short* pS = (short*)calloc(SIZE, sizeof(short));
for(i=0; i<SIZE; i++)
{
printf("pI[%d] = %d, pS[%d] = %d\n", i, pI[i], i, pS[i]);
}
printf("Before: pI = %p\n", pI);
pI = (int*)realloc(pI, 2 * SIZE * sizeof(int));
printf("After: pI = %p\n", pI);
for(i=0; i<10; i++)
{
printf("pI[%d] = %d\n", i, pI[i]);
}
free(pI);
free(pS);
return 0;
}
小结:
动态内存分配是 c 语言中的强大功能
程序能够在需要的时候有机会使用更多的内存
malloc 单纯的从系统中申请固定字节大小的内存
calloc 能以类型大小为单位申请内存并初始化为 0
realloc 用于重置内存大小
第 39 课 程序中的三国天下
程序中的栈
栈是现代计算机程序里最为重要的概念之一
栈在程序中用于维护函数调用上下文
函数中的参数和局部变量存储在栈上
栈保存了一个函数调用所需的维护信息
参数
返回地址
局部变量
调用上下文
函数调用过程
每次函数调用都对应着一个栈上的活动记录
调用函数的活动记录位于栈的中部
被调用函数的活动记录位于栈的顶部
函数调用的栈变化之一
从main()开始运行
函数调用的栈变化二
当main()调用 f()
函数调用的栈变化三
当从 f()调用返回 main()
函数调用栈上的数据
函数调用时,对应的栈空间在函数返回前是专用的函数调用时,栈空间将被释放,数据不再有效
int *g()
{
int a[10]={0};
return a;
}
void f()
{
int *pointer=g();
}
int main()
{
f();
return 0;
}
例 1 指向栈数据的指针
#include <stdio.h>
int* g()
{
int a[10] = {0};
return a;
}
void f()
{
int i=0;
int b[10]={0,1,2,3,4,5,6,7,8,9};
int* pointer = g();
for(i=0;i<10;i++)
{
b[i]=pointer[i];
}
for(i=0;i<10;i++)
{
printf("%d\n",b[i]);
}
}
int main()
{
f();
return 0;
}
程序中的堆
堆是程序中一块预留的内存空间,可由程序自由使用堆中被程序申请使用的内存在被主动释放前将一直有效为什么有了栈还需要堆?
栈上的数据在函数返回后就被释放掉,无法传递到函数外部,如:局部数组
c语言程序中通过库函数的调用获得堆空间
头文件:malloc.h
malloc:以字节的方式动态申请堆空间
free:将堆空间归还给系统
系统对堆空间的管理方式
空闲链表法,位图法,对象池法等等
程序中的静态存储区
静态存储区随着程序的运行而分配空间
静态存储区的声明周期直到程序运行结束
在程序的编译期静态存储区的大小就已经确定
静态存储区主要用于保存全局变量和静态局部变量静态存储区的信息最终会保存到可执行程序中
例 2 静态存储区的验证
#include <stdio.h>
int g_v = 1;
static int g_vs = 2;
void f()
{
static int g_vl = 3;
printf("%p\n", &g_vl);
}
int main()
{
printf("%p\n", &g_v);
printf("%p\n", &g_vs);
f();
return 0;
}
小结:
栈,堆和静态存储区是程序中的三个基本数据区
栈区主要用于函数调用的使用
堆区主要用于内存的动态申请和归还
静态存储区用于保存全局变量和静态变量
第 40 课 程序的内存布局
程序文件的一般布局
不同代码在可执行程序中的对应关系
int g_init_v=1; .data
int g_uninit_v; boss
void f(int i) .text
{
printf(“%d\n”,i);
}
int main(void)
{
static int s_vl=2; .data
static int s_v2; boss
int a=3;
int b;
f(s_v1+s_v2+a); .text
return 0;
}
程序与进程
程序和进程不同
程序是静态的概念,表现形式为一个可执行文件
进程是动态的概念,程序由操作系统加载运行后得到进程
每个程序可以对应多个进程
每个进程只能对应一个程序
思考
面试中的问题
包含脚本代码的文本文件是一种类型的可执行程序吗?如果是,对应什么样的进程呢?
运行—>可执行程序操作系统加载进程
运行脚本文件进程 脚本文件操作系统加载脚本解释程序进程
程序文件的一般布局
文件布局在内存中的映射
File Header 栈
.text 堆
.data .boss
.boss .data
a.out .text
未映射区域
a.out 进程的地址空间
程序的内存布局
各个段的作用
堆栈在程序运行后才正式存在,是程序运行的基础
.boss 段存放的是未初始化的全局变量和静态变量
.text 段存放的是程序中的可执行代码
.data 段保存的是已经初始化了的全局变量和静态变量
.rodata 段存放程序中的常量值,如字符串常量
程序术语的对应关系
静态存储区通常指程序中的.boss 和.data 段
只读存储区通常指程序中的.rodata 段
局部变量所占空间为栈上的空间
动态空间为堆中的空间
程序可执行代码存放于.text 段
思考
面试中的小问题
同是全局变量和静态变量,为什么初始化和未初始化的保存在不同段中?
小结:
程序源码在编译后对应可执行程序中的不同存储区程序和进程不同,程序静态概念,进程是动态概念
堆栈段是程序运行的基础,只存于进程空间中
程序可执行代码存放于.text 段,是只读的
.boss 和.data 段用于保存全局变量和静态变量
第 41 课 内存操作经典问题分析一
野指针
指针变量中的值是非法的内存地址,进而形成野指针
野指针不是 NULL 指针,是指向不可用内存地址的指针
NULL 指针并无危害,很好判断,也很好调试
c语言中无法判断一个指针所保存的地址是否合法
野指针的由来
局部指针变量没有被初始化
指针所指向的变量在指针之间被销毁
使用已经释放过的指针
进行了错误的指针运算
进行了错误的强制类型转换
例 1 野指针初探
#include <stdio.h>
#include <malloc.h>
int arr[40]={1,2,3,4,5,6,7};
int main()
{
int* p1 = (int*)malloc(40*sizeof(int));
//int* p2 = (int*)1234567; //p2是一个野指针
int *p2=arr; //p2合法
int i = 0;
for(i=0; i<40; i++)
{
*(p1 + i) = 40 - i; //由于指针运算产生了野指针,改写了非法的内存地址
}
free(p1);
for(i=0; i<40; i++)
{
p1[i] = p2[i]; //使用已经释放的内存空间
}
return 0;
}
基本原则
绝不返回局部变量和局部数组的地址
任何变量在定义后必须 0 初始化
字符数组必须确认 0 结束符后才能成为字符串
任何使用与内存操作相关的函数必须指定长度信息
例 2 无处不在的野指针
#include <stdio.h>
#include <string.h>
#include <malloc.h>
struct Student
{
char* name;
int number;
};
char* func()
{
char p[] = "D.T.Software";
return p;
}
void del(char* p)
{
printf("%s\n", p);
free(p);
}
int main()
{
struct Student s;
char* p = func();
strcpy(s.name, p);
s.number = 99;
p = (char*)malloc(5);
strcpy(p, "D.T.Software");
del(p);
return 0;
}
小结:
内存错误是实际产品开发中最常见的问题,然而绝大多数 bug 都可以通过遵循基本的编程原则和规范来避免
因此,在学习的时候要牢记和理解内存操作的基本原则,目的和意义
第 42 课 内存操作经典问题分析二
常见的内存错误
结构体成员指针未初始化
内存分配成功,但未初始化
内存操作越界
例 1 常见内存错误
#include <stdio.h>
#include <malloc.h>
void test(int* p, int size)
{
int i = 0;
for(i=0; i<size; i++)
{
printf("%d\n", p[i]);
}
free(p);
}
void func(unsigned int size)
{
int* p = (int*)malloc(size * sizeof(int));
int i = 0;
if( size % 2 != 0 )
{
free(p);
return;
}
for(i=0; i<size; i++)
{
p[i] = i;
printf("%d\n", p[i]);
}
free(p);
}
int main()
{
int* p = (int*)malloc(5 * sizeof(int));
test(p, 5);
//free(p);
func(9);
func(10);
return 0;
}
例 2 常见内存错误
#include <stdio.h>
#include <malloc.h>
struct Demo
{
char* p;
};
int main()
{
struct Demo d1;
struct Demo d2;
char i = 0;
for(i='a'; i<'z'; i++)
{
d1.p[i] = 0;
}
d2.p = (char*)calloc(5, sizeof(char));
printf("%s\n", d2.p);
for(i='a'; i<'z'; i++)
{
d2.p[i] = i;
}
free(d2.p);
return 0;
}
内存操作的交通规则
动态内存申请后,应该立即检查指针
值是否为 NULL,防止使用 NULL 指针
int *p=(int *)malloc(56);
if(p!=NULL)
{
//do something here!
}
free(p);
free 指针之后必须立即赋值为 NULL
int *p=(int *)malloc(20);
free(p);
p=NULL;
//….
//…
//…
if(p!=NULL)
{
//do something
}
任何与内存操作相关的函数都必须带长度信息
void print(int *p,int size)
{
int i=0;
char buf[128]={0};
snprintf(buf,sizeof(buf),”%s”,”hello world”);
for(i=0;i<size;i++)
{
printf(“%d\n”,p[i]);
}
}
malloc 操作和 free 操作必须匹配
防止内存泄漏和多次释放
void func()
{
int *p=(int *)malloc(20);
free(p);
}
int main()
{
int *p=(int *)malloc(40);
func();
free(p);
return 0;
}
小结:
内存错误的本质源于指针内存的地址为非法值
指针变量未初始化,保存随机值
指针运算导致内存越界
内存泄漏源于 malloc 和 free 不匹配
当malloc 次数多于 free 时,产生内存泄漏
当malloc 次数少于 free 时,程序可能崩溃
第43 课 函数的意义
c语言中的函数程序=数据+算法 c 程序=数据+函数
函数的意义
函数化程序设计
难以解决的复杂问题
/ | \
复杂问题 1 简单问题 复杂问题 2
/ \ / \
简单问题 1 简单问题 2 简单问题 1 简单问题 2
c 语言中的模块化
/ | \
模块 1 库函数 模块 2
/ \ / \
模块函数 1 模块函数 2 模块函数 1 模块函数 2
面向过程的程序设计
面向过程式一种以过程为中心的编程思想
首先将复杂的问题分解为一个个容易解决的问题
分解过后的问题可以按照步骤一步步完成
函数时面向过程在 c 语言中的体现
解决问题的每个步骤可以用函数来实现
声明和定义
函数声明?函数定义
声明的意义在于告诉编译器程序单元的存在
定义则明确指示程序单元的意义
c 语言中通过 extern 进行程序单元的声明
一些程序单元在声明时可以省略 extern
严格意义上的声明和定义并不相同
例 1 声明和定义的不同
#include <stdio.h>
#include <malloc.h>
extern int g_var;
extern struct Test;
int main()
{
extern void f(int i, int j);
extern int g(int x);
struct Test* p = NULL; // (struct Test*)malloc(sizeof(struct Test));
printf("p = %p\n", p);
//g_var = 10;
printf("g_var = %d\n", g_var);
f(1, 2);
printf("g(3) = %d\n", g(3));
free(p);
return 0;
}
#include <stdio.h>
int g_var = 10;
struct Test
{
int x;
int y;
};
void f(int i, int j)
{
printf("i + j = %d\n", i + j);
}
int g(int x)
{
return (int)(2 * x + g_var);
}
小结:
函数是面向过程思想在 c 语言中的体现
面向过程式由上至下分解问题的设计方法
程序中的定义和声明完全不同
c 语言中通过 extern 对程序单元进行声明
第 44 课 函数参数的秘密(上)
函数参数
函数参数在本质上与局部变量相同在栈上分配空间函数参数的初始值是函数调用时的实参值
函数参数的求值顺序依赖于编译器的实现
下面的程序输出什么?为什么?
int k=1;
printf(“%d,%d\n”,k++,k++);
例 1 函数参数的求值顺序
#include <stdio.h>
int func(int i, int j)
{
printf("%d, %d\n", i, j);
return 0;
}
int main()
{
int k = 1;
func(k++, k++);
printf("%d\n", k);
return 0;
}
程序中的顺序点
程序中存在一定的顺序点
顺序点指的是执行过程中修改变量值的最晚时刻
在程序到达顺序点的时候,之前所做的一切操作必须完成
c语言的顺序点
每个完整表达式结束时,即分号处
&&,||,?:,以及逗号表达式的每个参数计算之后
函数调用时所有实参求值完成后
函数调用时所有实参求值完成后(进入函数体之前)
下面的程序运行结束后 k 的值为多少?
int k=2;
k=k++ + k++;
例 2 程序中的顺序点
#include <stdio.h>
int main()
{
int k = 2;
int a = 1;
k = k++ + k++;
printf("k = %d\n", k);
if( a-- && a )
{
printf("a = %d\n", a);
}
return 0;
}
小结:
函数的参数在栈上分配空间
函数的实参并没有固定的计算次序
顺序点是 c 语言中变量修改的最晚时机
第 45 课 函数参数的秘密(下)
参数入栈顺序
函数参数的计算次序是依赖编译器实现的,那么函数参数的入栈次序是如何确定的呢?
strcpy(s,”hello world”);
调用约定
当函数调用发生时
参数会传递给被调用的函数
而返回值会被返回给函数调用者
调用约定描述参数如何传递到栈中以及栈的维护方式参数传递顺序
调用栈清理
调用约定是预定义的可理解为调用协议
调用约定通常用于库调用和库开发的时候
从右到左依次入栈:_stdcall,_cdecl,_thiscall
从左到右依次入栈:_pascal,_fastcall
小问题:
如何编写一个计算 n 个数平均值的函数?
例 1 编写函数计算平均值
#include <stdio.h>
float average(int array[], int size)
{
int i = 0;
float avr = 0;
for(i=0; i<size; i++)
{
avr += array[i];
}
return avr / size;
}
int main()
{
int array[] = {1, 2, 3, 4, 5};
printf("%f\n", average(array, 5));
return 0;
}
可变参数
c语言中可以定义参数可变的函数
参数可变函数的实现依赖于 stdarg.h 头文件
_va_list:参数集合
_va_arg:取具体参数值
_va_start:标识参数访问的开始_va_end:标识参数访问的结束
例 2 编写函数计算平均值
#include <stdio.h>
#include <stdarg.h>
float average(int n, ...)
{
va_list args;
int i = 0;
float sum = 0;
va_start(args, n);
for(i=0; i<n; i++)
{
sum += va_arg(args, int);
}
va_end(args);
return sum / n;
}
int main()
{
printf("%f\n", average(5, 1, 2, 3, 4, 5));
printf("%f\n", average(4, 1, 2, 3, 4));
return 0;
}
可变参数的限制
可变参数必须从头到尾按照顺序逐个访问
参数列表中至少要存在一个确定的命名参数
可变参数函数无法确定实际存在的参数的数量
可变参数函数无法确定参数的实际类型
注意:
va_arg 中如果指定了错误的类型,那么结果是不可预测的
小结:
调用约定指定了函数参数的入栈顺序以及栈的清理方式
可变参数是 c 语言提供的一种函数设计技巧
可变参数的函数提供了一种更方便的函数调用方式
可变参数必须顺序的访问,无法直接访问中间的参数值
第 46 课 函数与宏分析
函数与宏
宏 or 哈数?
#define RESET(p,len) \
while(len>0)\
((char*)p)[--len]=0
void reset(void *p,int len)
{
while(len>0)
((char*)p)[--len]=0;
}
函数与宏
宏是预处理器直接替换展开的,编译器不知道宏的存在函数是由编译器直接编译的,调用行为由编译器决定多次使用宏会导致最终可执行程序的体积增大函数是跳转执行的,内存中只有一份函数体存在
宏的效率比函数要高,因为是直接展开,无调用开销函数调用时会创建活动记录,效率不如宏
例 1 函数与宏
#include <stdio.h>
#define RESET(p, len) \
while( len > 0 ) \
((char*)p)[--len] = 0
void reset(void* p, int len)
{
while( len > 0 )
((char*)p)[--len] = 0;
}
int main()
{
int array[] = {1, 2, 3, 4, 5};
int len = sizeof(array);
int i = 0;
for(i=0; i<5; i++)
{
printf("array[%d] = %d\n", i, array[i]);
}
return 0;
}
宏的效率比函数稍高,但是副作用巨大
宏是文本替换,参数无法进行类型检查
可以用函数完成的功能绝对不用宏
宏的定义中不能出现递归定义
例 2 宏的副作用
#include <stdio.h>
#define _ADD_(a, b) a + b
#define _MUL_(a, b) a * b
#define _MIN_(a, b) ((a) < (b) ? (a) : (b))
int main()
{
int i = 1;
int j = 10;
printf("%d\n", _MUL_(_ADD_(1, 2), _ADD_(3, 4)));
printf("%d\n", _MIN_(i++, j));
return 0;
}
宏的妙用
用于生成一些常规性的代码
封装函数,加上类型信息
例 3 宏的妙用
#include <stdio.h>
#include <malloc.h>
#define MALLOC(type, x) (type*)malloc(sizeof(type)*x)
#define FREE(p) (free(p), p=NULL)
#define LOG_INT(i) printf("%s = %d\n", #i, i)
#define LOG_CHAR(c) printf("%s = %c\n", #c, c)
#define LOG_FLOAT(f) printf("%s = %f\n", #f, f)
#define LOG_POINTER(p) printf("%s = %p\n", #p, p)
#define LOG_STRING(s) printf("%s = %s\n", #s, s)
#define FOREACH(i, n) while(1) { int i = 0, l = n; for(i=0; i < l; i++)
#define BEGIN {
#define END } break; }
int main()
{
int* pi = MALLOC(int, 5);
char* str = "D.T.Software";
LOG_STRING(str);
LOG_POINTER(pi);
FOREACH(k, 5)
BEGIN
pi[k] = k + 1;
END
FOREACH(n, 5)
BEGIN
int value = pi[n];
LOG_INT(value);
END
FREE(pi);
LOG_POINTER(pi);
return 0;
}
小结:
宏和函数并不是竞争对手
宏能接受任何类型的参数,效率高,易出错
函数的参数必须是固定类型,效率稍低,不易出错宏可以实现参数不能实现的功能
第 47 课 递归函数分析
递归的数学思想
递归是一种数学上分而自治的思想
递归需要有边界条件
当边界条件不满足时,递归继续进行
当边界条件满足时,递归停止
递归将大型复杂问题转换为与原问题相同但规模较小的问题进行处理。
递归函数
函数体内部可以调用自己
递归函数
函数体中存在自我调用的函数
递归函数时递归的数学思想在程序设计中的应用
递归函数必须有递归出口
函数的无限递归将导致程序栈溢出而崩溃
递归函数设计技巧
递归模型的一般表示法
例1 递归版 strlen
#include <stdio.h>
int strlen_r(const char* s)
{
if( *s )
{
return 1 + strlen_r(s+1);
}
else
{
return 0;
}
}
int main()
{
printf("%d\n", strlen_r("abc"));
printf("%d\n", strlen_r(""));
return 0;
}
例2 菲薄拉契数列
#include <stdio.h>
int fac(int n)
{
if( n == 1 )
{
return 1;
}
else if( n == 2 )
{
return 1;
}
else
{
return fac(n-1) + fac(n-2);
}
return -1;
}
int main()
{
printf("%d\n", fac(1));
printf("%d\n", fac(2));
printf("%d\n", fac(9));
return 0;
}
例3、汉诺塔问题
#include <stdio.h>
void han_move(int n, char a, char b, char c)
{
if( n == 1 )
{
printf("%c --> %c\n", a, c);
}
else
{
han_move(n-1, a, c, b);
han_move(1, a, b, c);
han_move(n-1, b, a, c);
}
}
int main()
{
han_move(3, 'A', 'B', 'C');
return 0;
}
将木块借助 B 柱由 A 移动到 C 柱
每次只移动一个木块
只能出现小木块在大木块之上
例 3 汉诺塔问题求解
小结:
递归是一种将问题分而自治的思想
用递归解决问题首先要建立递归的模型
递归解法必须要有边界条件,否则无解
第 48 课 函数设计原则(完结)
函数设计原则
函数从意义上应该是一个独立的功能模块
函数名要在一定程度上反应函数的功能
函数参数名要能够体现参数的意义
尽量避免在函数中使用全局变量
void sc(char *s1,char *s2);
void str_copy(char *str_dest,char *str_src);
当函数参数不应该在函数体内部被修改时,应加上 const 声明如果参数是指针,且仅作输入参数,则应加上 const 声明
void str_copy(char *str_dest,const char *str_src);
不能省略返回值的类型
如果函数没有返回值,那么应声明为 void 类型对参数进行有效性检查
对于指针参数的检查尤为重要
不要返回指向“栈内存”的指针
栈内存在函数体结束时被自动释放
函数的规模要小,尽量控制在 80 行代码之内
相同的输入对应相同的输出,避免函数带有“记忆”功能避免函数有过多的参数,参数个数尽量控制在 4 个以内
有时候函数不需要返回值,但为了增加灵活性,如支持链式表达,可以附加返回值
char s[64];
int len=strlen(strcpy(s,”android”));
函数名与返回值类型在语义上不可冲突
char c=getchar();
if(EOF==c)
{
}
例 1 优秀代码赏析
/*******************************************************************************
* Copyright (c) 2000, 2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Kevin Cornell (Rational Software Corporation)
*******************************************************************************/
/* Eclipse Launcher Utility Methods */
#include "eclipseOS.h"
#include "eclipseCommon.h"
#include "eclipseUtil.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#ifdef _WIN32
#include <direct.h>
#else
#include <unistd.h>
#include <strings.h>
#endif
#define MAX_LINE_LENGTH 256
/* Is the given VM J9 */
int isJ9VM( _TCHAR* vm )
{
_TCHAR * ch = NULL, *ch2 = NULL;
int res = 0;
if (vm == NULL)
return 0;
ch = lastDirSeparator( vm );
if (isVMLibrary(vm)) {
/* a library, call it j9 if the parent dir is j9vm */
if(ch == NULL)
return 0;
ch[0] = 0;
ch2 = lastDirSeparator(vm);
if(ch2 != NULL) {
res = (_tcsicmp(ch2 + 1, _T_ECLIPSE("j9vm")) == 0);
}
ch[0] = dirSeparator;
return res;
} else {
if (ch == NULL)
ch = vm;
else
ch++;
return (_tcsicmp( ch, _T_ECLIPSE("j9") ) == 0);
}
}
int checkProvidedVMType( _TCHAR* vm )
{
_TCHAR* ch = NULL;
struct _stat stats;
if (vm == NULL) return VM_NOTHING;
if (_tstat(vm, &stats) == 0 && (stats.st_mode & S_IFDIR) != 0) {
/* directory */
return VM_DIRECTORY;
}
ch = _tcsrchr( vm, _T_ECLIPSE('.') );
if(ch == NULL)
return VM_OTHER;
#ifdef _WIN32
if (_tcsicmp(ch, _T_ECLIPSE(".dll")) == 0)
#else
if ((_tcsicmp(ch, _T_ECLIPSE(".so")) == 0) || (_tcsicmp(ch, _T_ECLIPSE(".jnilib")) == 0) || (_tcsicmp(ch, _T_ECLIPSE(".dylib")) == 0))
#endif
{
return VM_LIBRARY;
}
if (_tcsicmp(ch, _T_ECLIPSE(".ee")) == 0)
return VM_EE_PROPS;
return VM_OTHER;
}
/*
* pathList is a pathSeparator separated list of paths, run each through
* checkPath and recombine the results.
* New memory is always allocated for the result
*/
_TCHAR * checkPathList( _TCHAR* pathList, _TCHAR* programDir, int reverseOrder) {
_TCHAR * c1, *c2;
_TCHAR * checked, *result;
size_t checkedLength = 0, resultLength = 0;
size_t bufferLength = _tcslen(pathList);
result = malloc(bufferLength * sizeof(_TCHAR));
c1 = pathList;
while (c1 != NULL && *c1 != _T_ECLIPSE('\0'))
{
c2 = _tcschr(c1, pathSeparator);
if (c2 != NULL)
*c2 = 0;
checked = checkPath(c1, programDir, reverseOrder);
checkedLength = _tcslen(checked);
if (resultLength + checkedLength + 1> bufferLength) {
bufferLength += checkedLength + 1;
result = realloc(result, bufferLength * sizeof(_TCHAR));
}
if(resultLength > 0) {
result[resultLength++] = pathSeparator;
result[resultLength] = _T_ECLIPSE('\0');
}
_tcscpy(result + resultLength, checked);
resultLength += checkedLength;
if(checked != c1)
free(checked);
if(c2 != NULL)
*(c2++) = pathSeparator;
c1 = c2;
}
return result;
}
_TCHAR * concatStrings(_TCHAR**strs) {
return concatPaths(strs, 0);
}
_TCHAR * concatPaths(_TCHAR** strs, _TCHAR separator) {
_TCHAR separatorString[] = { separator, 0 };
_TCHAR * result;
int i = -1;
size_t length = 0;
/* first count how large a buffer we need */
while (strs[++i] != NULL) {
length += _tcslen(strs[i]) + (separator != 0 ? 1 : 0);
}
result = malloc((length + 1) * sizeof(_TCHAR));
result[0] = 0;
i = -1;
while (strs[++i] != NULL) {
result = _tcscat(result, strs[i]);
if (separator != 0)
result = _tcscat(result, separatorString);
}
return result;
}
/*
* buffer contains a pathSeparator separated list of paths, check
* that it contains all the paths given. Each path is expected to be
* terminated with a pathSeparator character.
*/
int containsPaths(_TCHAR * str, _TCHAR** paths) {
_TCHAR * buffer;
_TCHAR * c;
int i;
/* terminate the string with a pathSeparator */
buffer = malloc((_tcslen(str) + 2) * sizeof(_TCHAR));
_stprintf(buffer, _T_ECLIPSE("%s%c"), str, pathSeparator);
for (i = 0; paths[i] != NULL; i++) {
c = _tcsstr(buffer, paths[i]);
if ( c == NULL || !(c == buffer || *(c - 1) == pathSeparator))
{
/* entry not found */
free(buffer);
return 0;
}
}
free(buffer);
return 1;
}
int isVMLibrary( _TCHAR* vm )
{
_TCHAR *ch = NULL;
if (vm == NULL) return 0;
ch = _tcsrchr( vm, '.' );
if(ch == NULL)
return 0;
#ifdef _WIN32
return (_tcsicmp(ch, _T_ECLIPSE(".dll")) == 0);
#else
return (_tcsicmp(ch, _T_ECLIPSE(".so")) == 0) || (_tcsicmp(ch, _T_ECLIPSE(".jnilib")) == 0) || (_tcsicmp(ch, _T_ECLIPSE(".dylib")) == 0);
#endif
}
#ifdef AIX
#include <sys/types.h>
#include <time.h>
/* Return the JVM version in the format x.x.x
*/
char* getVMVersion( char *vmPath )
{
char cmd[MAX_LINE_LENGTH];
char lineString[MAX_LINE_LENGTH];
char* firstChar;
char fileName[MAX_LINE_LENGTH];
time_t curTime;
FILE* fp;
int numChars = 0;
char* version = NULL;
/* Define a unique filename for the java output. */
(void) time(&curTime);
(void) sprintf(fileName, "/tmp/tmp%ld.txt", curTime);
/* Write java -version output to a temp file */
(void) sprintf(cmd,"%s -version 2> %s", vmPath, fileName);
(void) system(cmd);
fp = fopen(fileName, "r");
if (fp != NULL)
{
/* Read java -version output from a temp file */
if (fgets(lineString, MAX_LINE_LENGTH, fp) == NULL)
lineString[0] = '\0';
fclose(fp);
unlink(fileName);
/* Extract version number */
firstChar = (char *) (strchr(lineString, '"') + 1);
if (firstChar != NULL)
numChars = (int) (strrchr(lineString, '"') - firstChar);
/* Allocate a buffer and copy the version string into it. */
if (numChars > 0)
{
version = malloc( numChars + 1 );
strncpy(version, firstChar, numChars);
version[numChars] = '\0';
}
}
return version;
}
/* Compare JVM Versions of the form "x.x.x..."
*
* Returns -1 if ver1 < ver2
* Returns 0 if ver1 = ver2
* Returns 1 if ver1 > ver2
*/
int versionCmp(char *ver1, char *ver2)
{
char* dot1;
char* dot2;
int num1;
int num2;
dot1 = strchr(ver1, '.');
dot2 = strchr(ver2, '.');
num1 = atoi(ver1);
num2 = atoi(ver2);
if (num1 > num2)
return 1;
if (num1 < num2)
return -1;
if (dot1 && !dot2) /* x.y > x */
return 1;
if (!dot1 && dot2) /* x < x.y */
return -1;
if (!dot1 && !dot2) /* x == x */
return 0;
return versionCmp((char*)(dot1 + 1), (char*)(dot2 + 1) );
}
#endif /* AIX */
课程总结:
c语言的学习需要思考勤动手才能得到提高难点部分为指针的学习
指针的本质,指针的运算,指针和数组学习过程可以采用各个击破的方法,在一个特定的时间段只重点学习和练习某个主题,在熟练掌握 c 语言的各个特性后再进行项目练习