原文网址:http://bbs.elecfans.com/jishu_354666_1_1.html
再过1个月又是一年应届毕业生应聘的高峰期了,为了方便应届毕业生应聘,笔者将大学四年C语言知识及去年本人C语言笔试难点进行梳理,希望能对今年应届毕业生的应聘有所帮助。
2013年10月18日更新--> 攻破C语言这个帖子更新到这里,我不仅仅是为了补充大学学生遗漏的知识,我更重要的是希望通过我的经验,你们实际项目中的C语言写得漂亮,写出属于你的风格。“朱兆祺STM32手记”(http://bbs.elecfans.com/jishu_385613_1_1.html)一帖中我就多次强调过编程的模块化,要考虑到可移植性和别人的可阅读性。我在某公司实习的时候,拿到一个程序,我当时就想吐槽,我想除了这个程序的当时写作者能够看懂之外,其他人有谁还能看懂,程序构架、可移植性性就更是一塌糊涂。结果我全部推到重写。因此明志电子科技工作室从承接第一个项目做开始,我便和搭档说,我们必须制定我们工作室的编程规范和编程风格,这样就算给谁,换成谁,拿到项目都能马上接下去做。
朱兆祺在这个帖子将会不断更新,明志电子工作室的项目经验也将在电子发烧友论坛不断贴出,我希望能用我们仅有的力量,将我们的经验毫不保留传授给大家。我只希望在未来某一天,有那么几个人会竖着大拇指说:明志电子科技工作室的经验受益匪浅就够了。我相信,在深圳,明志电子科技工作室的影响力会日益增长,因为我们已经规划好了未来脚步。
C语言是一门技术,但更是一门艺术。写一个C语言代码不难,写一个高水平、漂亮的代码很难。朱兆祺将在此帖不断为大家攻破C语言。
<--朱兆祺于2013年10月18日
-->朱兆祺更新于2014年2月21日:
深圳市馒头科技有限公司于2014年2月注册成立,当初命名为馒头科技,不管是吴坚鸿所说亲切还是谢贤斌所说草根。馒头科技永远以用户满意为我们的追求,馒头科技不追求锦上添花,但是我们很愿意为每一位客户雪中送炭。因为锦上添花每一个人都会做,但是雪中送炭却不是每一个人愿意去做。
馒头科技往后将为每一位读者送上更为精美的学习资料。敬请期待。
第一节 C语言编程中的几个基本概念
http://bbs.elecfans.com/jishu_354666_1_1.html
第二节 数据存储与变量
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2088253&fromuid=222350
第三节 数学算法解决C语言问题
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2088283&fromuid=222350
第四节 关键字、运算符与语句
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2088352&fromuid=222350
第五节 C语言中的细节
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2088375&fromuid=222350
第六节 数组与指针
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2088417&fromuid=222350
第七节 结构体与联合体
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2088582&fromuid=222350
第八节 内存分配与内存释放
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2088596&fromuid=222350
第九节 笔试中的几个常考题
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2088606&fromuid=222350
第十节 数据结构之冒泡排序、选择排序
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2092632&fromuid=222350
第十一节 机试题之数据编码
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2096393&fromuid=222350
第十二节 机试题之十进制1~N的所有整数中出现“1”的个数
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2097513&fromuid=222350
第十三节 机试题之 遍历单链表一次,找出链表中间元素
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2103563&fromuid=222350
第十四节 机试题之全排序
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2105648&fromuid=222350
第十五节 机试题之大整数运算
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2109737&fromuid=222350
第十六节 机试题之大整数减法与乘法
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2115675&fromuid=222350
第十七节 算法之二分查找
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2128027&fromuid=222350
第十八节 数据结构之单向链表(颠覆你手中数据结构的三观)
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2139670&fromuid=222350
第十九节 数据结构之双向链表(接着颠覆你手中的数据结构三观)
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2156978&fromuid=222350
第二十节 数据结构之栈
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2193337&fromuid=222350
C语言技术公开课第一讲——编译环境给C语言带来的困扰,网络课程讲义
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2230571&fromuid=222350
第二十一节 通过加减法高效的求出两个无符号整数的商和余数
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2252461&fromuid=222350
第二十二节 表达式计算器(1)
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2280837&fromuid=222350
第二十三节 表达式计算器(2)
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2325485&fromuid=222350
第二十四节 表达式计算器(3)
http://bbs.elecfans.com/forum.ph ... 4547&fromuid=222350
第二十五节 表达式计算器(4)
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2395966&fromuid=222350
C语言技术公开课第三讲 const问题
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2434706&fromuid=222350
第二十六节 序列差最小
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2466868&fromuid=222350
第二十七节 sizeof与strlen的深入
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=354666&pid=2676569&fromuid=222350
1.1 #include< >与#include" "
1.2 switch()
1.3 const
1.4 #ifndef/#define/#endif
1.5 全局变量和局部变量
2.1 变量的声明与定义
2.2 局部变量与全局变量的较量
2.3 char、int、float、double的数据存储
图2. 1 数据存储方式
|
2.4 容易忽略char的范围
取字符型数据
|
由于( (int)(&b)+1 )是整型数据类型,通过(int *)( (int)(&b)+1 )转化为了整型指针类型,说明要占4个字节,即为:0x12ff55、0x12ff56、0x12ff57、0x12ff58,再去地址*( (int *)( (int) (&b)+1 ) )得到存储在这4个字节中的数据。但是很遗憾,0x12ff58我们并不知道存储的是什么,所以我们只能写出0x**0012ff。**表示存储在0x12ff58中的数据。如图2. 2所示。
取整型数据
|
3.2 N!结果中的位数
第四节 关键字、运算符与语句 1.1 static 1. 如程序清单4. 1所示,请问输出i、j的结果?
程序清单4. 1 static
#include <stdio.h>
static int j ;
void fun1(void)
{
static int i = 0 ;
i++ ;
printf("i = %d " , i );
}
void fun2(void)
{
j = 0 ;
j++ ;
printf("j = %d \n" , j );
}
int main(int argc, char *argv[])
{
int k = 0 ;
for( k = 0 ; k < 10 ; k++ ){
fun1() ;
fun2() ;
printf("\n");
}
return 0;
}
答案:
i = 1 j = 1
i = 2 j = 1
i = 3 j = 1
i = 4 j = 1
i = 5 j = 1
i = 6 j = 1
i = 7 j = 1
i = 8 j = 1
i = 9 j = 1
i = 10 j = 1
请按任意键继续. . .
很多人傻了,为什么呢?是啊,为什么呢?!
由于被static修饰的变量总存在内存静态区,所以运行这个函数结束,这个静态变量的值也不会被销毁,函数下次使用的时候仍然能使用这个值。
有人就问啊,为什么j一直是1啊。因为每次调用fun2()这个函数,j都被强行置0了。
static的作用:
(1) 函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次, 因此其值在下次调用时仍维持上次的值;
(2) 在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
(3) 在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明 它的模块内;
(4) 在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(5) 在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的static 成员变量。
1.2 for循环的深究 1. 如程序清单4. 2所示,输出结果是什么?
程序清单4. 2 for循环
#include <stdio.h>
int main(int argc, char *argv[])
{
int i = 0 ;
for( i = 0 ,printf("First = %d " , i ) ;
printf("Second = %d " , i ) , i < 10 ;
i++ , printf("Third = %d " , i ))
{
printf("Fourth = %d \n" , i) ;
}
return 0;
}
这个题目主要考察对for循环的理解。我们先来看看运行程序会输出什么?
First = 0 Second = 0 Fourth = 0
Third = 1 Second = 1 Fourth = 1
Third = 2 Second = 2 Fourth = 2
Third = 3 Second = 3 Fourth = 3
Third = 4 Second = 4 Fourth = 4
Third = 5 Second = 5 Fourth = 5
Third = 6 Second = 6 Fourth = 6
Third = 7 Second = 7 Fourth = 7
Third = 8 Second = 8 Fourth = 8
Third = 9 Second = 9 Fourth = 9
Third = 10 Second = 10 请按任意键继续. . .
从输出我们就可以知道程序到底是什么运行:
首先i = 0 , 所以输出:First = 0 ; 接着输出:Second = 0 ; i < 10 成立,则输出:Fourth = 0 。就此完成第一个循环。接着 i ++ , 此时i = 1 ,输出:Third = 1 ;接着输出:Second = 1 ;i < 10 成立,则输出:Fourth = 1 ······以此类推。
1.3 尺子——sizeof 1. 如程序清单4. 3所示,sizeof(a),sizeof(b)分别是多少?
程序清单4. 3 sizeof
#include <stdio.h>
int main(int argc, char *argv[])
{
char a[2][3] ;
short b[2][3] ;
printf( "sizeof(a) = %d \n" , sizeof( a ) ) ;
printf( "sizeof(b) = %d \n" , sizeof( b ) ) ;
return 0;
}
这个题目比较简单,由于char 是1个字节、short是2个字节,所以本题答案是:
sizeof(a) = 6
sizeof(b) = 12
请按任意键继续. . .
好的,再来看看如程序清单4. 4所示,sizeof(a),sizeof(b)分别是多少?
程序清单4. 4 sizeof
#include <stdio.h>
int main(int argc, char *argv[])
{
char *a[2][3] ;
short *b[2][3] ;
printf( "sizeof(a) = %d \n" , sizeof( a ) ) ;
printf( "sizeof(b) = %d \n" , sizeof( b ) ) ;
return 0;
}
是数组指针呢,还是指针数组呢?这里涉及*和[]和优先级的问题。我告诉大家的是这两个数组存放的都是指针变量,至于为什么,在后续章节会详细解释,然而指针变量所占的字节数为4字节,所以答案:
sizeof(a) = 24
sizeof(b) = 24
请按任意键继续. . .
1.4 ++i和i++ 1. 或许大家都知道,++i是先执行i自加再赋值,但是i++是先赋值再自加,但是还有隐藏在后面的东西呢?
int i = 0 ;
int iNumber = 0 ;
iNumber = (++i) + (++i) + (++i) ;
,有的编译器输出是:9。这两个答案都是对的,编译器不同所不同。7 = 2+2+3;9=3+3+3。区别在于答案是7的先执行(++i)+(++i)再执行+(++i),但是答案是9的是一起执行。
这只是前奏,先看几个让你目瞪口呆的例子。编译环境是VS2010。
int i = 0 ;
int iNumber = 0 ;
iNumber = (i++) + (++i) + (++i) ;
printf( "iNumber = %d \n" , iNumber ) ;
这里输出是:4!!!4 = 1+1+2。
int i = 0 ;
int iNumber = 0 ;
iNumber = (++i) + (i++) + (++i) ;
printf( "iNumber = %d \n" , iNumber ) ;
这里输出是:4!!!4=1+1+2。
int i = 0 ;
int iNumber = 0 ;
iNumber = (++i) + (++i) + (i++) ;
printf( "iNumber = %d \n" , iNumber ) ;
这里输出是:6!!!6=2+2+2。
这里至少能说明两个问题,其一,先执行前面两个,再执行第三个;其二,(i++)这个i的自加是最后执行!
int i = 0 ;
int iNumber = 0 ;
iNumber = (++i) + (i++) + (++i) + (++i) + (i++) ;
printf( "iNumber = %d \n" , iNumber ) ;
这个会是多少?!答案是:10!!!10=1+1+2+3+3!
不同的编译器或许会存在不同的答案,希望读者自行进行验证。
1.5 scanf()函数的输入 1. 如程序清单4. 5所示,运行程序,当显示Enter Dividend: , 输入的是a,按下Enter之后程序会怎么运行?
程序清单4. 5 scanf()函数的输入
#include<stdio.h>
int main(void)
{
float fDividend,fDivisor,fResult;
printf("Enter Dividend:");
scanf("%f",&fDividend);
printf("Enter Divisor:");
scanf("%f",&fDivisor);
fResult=fDividend/fDivisor;
printf("Result is: %f\n",fResult);
return 0;
}
这个问题有人会说,肯定是显示Enter Divisor:要我输入除数咯。是这样吗?
答案是:如果你在Enter Dividend:之后输入非数字,按下Enter之后显示的不是Enter Divisor: 要你输入除数,而是程序到此就运行结束,显示一个不确定答案,这个答案每一次都会变。如果你在Enter Divisor:之后输入非数字,按下Enter之后显示的不是Reslut的结果, 而是程序到此就运行结束,显示一个不确定答案,这个答案每一次都会变。
由于scanf()使用了%f,当输入数字的时候,scanf()将缓冲区中的数字读入fDividend,并清空缓冲区。由于我们输入的并非数字,因此scanf()在读入失败的同时并不会清空缓冲区。最后的的结果是,我们不需要再输入其他字符,scanf()每次都会去读取缓冲区,每次都失败,每次都不会清空缓冲区。当执行下一条scanf()函数读取除数时,由于缓冲区中有数据,因此它不等待用户输入,而是直接从缓冲区中取走数据。
那么防止输入非数字的程序应该怎样呢?
#include<stdio.h>
int main( int argc , char *argv[] )
{
float fDividend , fDivisor , fResult ;
int iRet ;
char cTmp1[ 256 ] ;
printf( "Enter Dividend \n" ) ;
iRet = scanf( "%f" , &fDividend ) ;
if ( 1 == iRet )
{
printf( "Enter Divisor \n" ) ;
iRet = scanf( "%f" , &fDivisor ) ;
if ( 1== iRet )
{
fResult = fDividend / fDivisor ;
printf( "Result is %f \n" , fResult ) ;
}
else
{
printf( "Input error ,not a number! \n" ) ;
gets(cTmp1) ;
return 1 ;
}
}
else
{
printf( "Input error , not a number! \n" ) ;
gets(cTmp1) ;
return 1 ;
}
return 0 ;
}
1.6 scanf()函数的返回值 1. 如程序清单4. 6所示,请问输出会是什么?
程序清单4. 6 scanf()函数的返回值
int a , b ;
printf( "%d \n" , scanf("%d%d" , &a , &b) ) ;
输出输入这个函数的返回值?!答案是:2。只要你合法输入,不管你输入什么,输出都是2。那么我们就要深入解析scanf这个函数。scanf()的返回值是成功赋值的变量数量。
1.7 const作用下的变量 1. 阅读如程序清单4. 7所示,想想会输出什么?为什么?
程序清单4. 7 const作用下的变量
const int iNumber = 10 ;
printf(" iNumber = %d \n" , iNumber) ;
int *ptr = (int *)(&iNumber) ;
*ptr = 100 ;
printf(" iNumber = %d \n" , iNumber) ;
const的作用在第四章已经详细讲了,这里就不再累赘,答案是:10,10。这里补充一个知识点:
const int *p 指针变量p可变,而p指向的数据元素不能变
int* const p 指针变量p不可变,而p指向的数据元素可变
const int* const p 指针变量p不可变,而p指向的数据元素亦不能变
1.8 *ptr++、*(ptr++),*++ptr、*(++ptr),++(*ptr)、(*ptr)++的纠缠不清 1. 如程序清单4. 8所示程序,输出什么?
程序清单4. 8 *ptr++
int iArray[3] = { 1 , 11 , 22} ;
int *ptr = iArray ;
printf( "*ptr++ = %d \n" , *ptr++ ) ;
printf( "*ptr = %d \n" , *ptr ) ;
纠结啊,是先算*ptr还是ptr++;还是纠结啊,ptr是地址加1还是偏移一个数组元素!
这里考查了两个知识点,其一:*与++的优先级问题;其二,数组i++和++i的问题。*和++都是优先级为2,且都是单目运算符,自右向左结合。所以这里的*ptr++和*(ptr++)是等效的。
首先ptr是数组首元素的地址,所以ptr++是偏移一个数组元素的地址。那么ptr++运算完成之后,此时的ptr是指向iArray[1],所以第二个输出*ptr = 11 。如图4. 1所示。那么倒回来看第一个输出,ptr++是在执行完成*ptr++之后再执行的,所以,*ptr++ = 1 。
如程序清单4. 9所示程序,输出会是什么?
程序清单4. 9 *++ptr
int iArray[3] = { 1 , 11 , 22} ;
int *ptr = iArray ;
printf( "*++ptr = %d \n" , *++ptr ) ;
printf( "*ptr = %d \n" , *ptr ) ;
这个解释和上面解释差不多,就是++ptr和ptr++的差别,所以这里的两个输出都是:11。同样的道理,*++ptr和*(++ptr)是等效。
再如程序清单4. 10所示,输出又会是什么?
程序清单4. 10 (*ptr)++
int iArray[3] = { 1 , 11 , 22} ;
int *ptr = iArray ;
printf( "(*ptr)++ = %d \n" , (*ptr)++ ) ;
printf( "*ptr = %d \n" , *ptr ) ;
这个的输出是:1,2。原因请读者分析。
|
-
4.jpg (94.96 KB, 下载次数: 21)
图4.1 ptr++
1.1 “零值”比较
1.2 宏定义
1.3 递归运算
1.4 让人忽略的贪心法
1.5 性能优化
1.1 数组、数组元素、指针的大小
1.2 广东省的省*和广州市的市*都在广州
iArray是数组名,由6.1节对a的说明,可iArray知同时也是数组首元素的地址,为0x1000 0000;&iArray是数组的首地址,这是毫无疑问的。&iArray[0]是数组首元素的地址,也是0x1000 0000。也就是说数组的首地址和数组首元素的地址是相等的。因为广东省的省*和广东省的首号城市广州市的市*都在广州,两者的地址是相等的。如图6. 1所示。
1.3 数组作为函数参数,是传什么进去了
1.4 指针相减
到底是加什么
1.6 数组与指针的概念
b) int *a;
c) int **a;
d) int a[10];
e) int *a[10];
f) int (*a)[10];
g) int (*a)(int);
h) int (*a[10])(int);
1.7 数组与指针的糅合
1.8 指针数组
1.9 数组指针
1.10 再论数组指针与数组首地址
1.1 结构体内存对齐问题
1.1 结构体在STM32的应用
1.2 结构体与指针
1.3 联合体的存储
1.1 结构体在联合体中的声明
1.2 结构体与联合体的内存
1.3 再论联合体与结构体
u.c和u.s的存储方式如图7. 5所示。
1.1 malloc
1.2 malloc(0)
1.1 strcpy
1.2 CPU的使用率
的个数
1.4 二进制高位到低位实现逆变
1.5 大小端测试
1.6 二分查找
1.7 int (*p[10])(int)
1.8 对绝对地址赋值的问题
我相信很多人曾经写冒泡排序和选择排序都是一个算法一个代码,并且还一个一个
写得不亦乐乎。zzq宁静致远今天就告诉你如何写出一手漂亮的C语言代码,当你看完
今天的帖子,你就会恍然顿悟曾经自己写的代码如此不堪。
1. 冒泡排序
1.1 底层冒泡排序的头文件
为了增强代码的可移植性,健壮性。我们将冒泡排序的算法封装在库中,我们只需要调用库函数即可。冒泡排序头文件程序如程序清单1. 1所示。
程序清单1. 1 冒泡排序头文件
/*
* 声明比较函数,升序还是降序
*/
typedef int (*COMPAREFUN)(const void *pvData1, const void *pvData2);
/*******************************************************************************
**函数名称: bubbleSort
**函数功能: 冒泡排序
**入口参数: *pvData: 需要进行排序的数组
stAmount: 数组中包含的元素个数
stSize: 元素内存大小
CompareFun: 需要排序的数据类型、升序还是降序
**出口参数:
*******************************************************************************/
extern void bubbleSort (void *pvData, size_t stAmount, size_t stSize , COMPAREFUN CompareFun);
为了各种数据的兼容性,所有不确定情况的数据类型都使用void *。
1.2 底层数据交换函数实现
通过函数指针类型数据,向swap函数传入需要交换的两个数据的地址和数组元素内存大小,实现数据的交换。swap函数代码如程序清单1. 2所示。
程序清单1. 2 swap函数
/*******************************************************************************
**函数名称: __swap
**函数功能: 数据交换
**入口参数: *pvData1: 需要交换的数据一
*pvData2: 需要交换的数据二
stDataSize:元素内存大小
**出口参数:
*******************************************************************************/
static void __swap (void *pvData1, void *pvData2, size_t stDataSize)
{
unsigned int *p1=(unsigned int)pvData1;
unsigned int *p2=(unsigned int)pvData2;
unsigned int uiTemp;
while ( stDataSize >= sizeof(unsigned int) ) //一次交换sizeof(unsigned int)个字节
{
(*p1) ^=(*p2);
(*p2) ^=(*p1);
(*p1++)^=(*p2++);
stDataSize -= sizeof(unsigned int);
}
if (stDataSize>0)
{
/*
* void *memmove( void *to, const void *from, size_t count );
* 函数从from中复制count 个字符到to中,并返回to指针。
*/
memmove( &uiTemp,p1,stDataSize);
memmove( p1,p2,stDataSize);
memmove( p2,&uiTemp,stDataSize);
}
}
这里传进去的是三个参数分别是:pvData1,为需要交换的两个数据中的第一个数据的地址;pvData2,为需要交换的两个数据中的第二个数据的地址;stDataSize:数组中元素内存的大小。
传进去之后,先将两个数据的地址强制转化为(int*)型地址。数据的交换分成两个部分:如果元素内存大小大于一个sizeof(unsigned int)个字节大小,则一次性交换4位;当stDataSize大于0且小于一个sizeof(unsigned int)个字节大小时,再通过memmove交换剩下的部分。
1.3 冒泡排序算法实现
冒泡排序的基本思想是通过相邻元素之间的比较和交换使得排序码较小的元素上移或下移。冒泡排序代码如程序清单1. 3所示。
程序清单1. 3 冒泡排序
/*******************************************************************************
**函数名称: bubbleSort
**函数功能: 冒泡排序
**入口参数: *pvData: 需要进行排序的数组
stAmount: 数组中包含的元素个数
stSize: 元素内存大小
CompareFun: 需要排序的数据类型、升序还是降序
**出口参数:
*******************************************************************************/
void bubbleSort (void *pvData, size_t stAmount, size_t stSize , COMPAREFUN CompareFun)
{
int i, j;
int iNoSwapFlg = 0;
void *pvThis = NULL;
void *pvNext = NULL;
/*
* 冒泡排序
*/
i = stAmount - 1;
do {
iNoSwapFlg = 0;
pvThis = pvData;
pvNext = (char *)pvData + stSize;
j = i;
do {
if (CompareFun(pvThis, pvNext) > 0) {
__swap(pvThis, pvNext, stSize);
iNoSwapFlg = 1;
}
pvThis = pvNext;
pvNext = (char *)pvNext + stSize;
} while (--j != 0);
if (iNoSwapFlg == 0) {
break;
}
} while ( --i != 0);
}
bubbleSort函数传入的有四个参数:pvData:需要进行排序的数组的首元素地址,但是这个地址也就是需要进行排序的数组的地址。这个区别就好像是广东的省*在广州,而广东省首号城市广州市的市*也在广州,虽然地址相同,但是意义不同。为了证明这个观点,我定义了两个数组进行测试。
static int iArray[] = {39, 33, 18, 64, 73, 30, 49, 51, 81};
static char *strArray[] ={"forARM","mzdzkj","c language","shenzhen","china"};
printf("&iArray = %#x \n" , &iArray ) ;
printf("&iArray[0] = %#x \n" , &iArray[0] ) ;
printf("strArray = %#x \n" , strArray ) ;
printf("&strArray = %#x \n" , &strArray ) ;
编译之后运行的结果为:
&iArray = 0x402000
&iArray[0] = 0x402000
strArray = 0x402024
&strArray = 0x402024
所以在这个函数中,无论传入的是数组的首元素地址,还是数组的地址,都是可以的,因为有这么一句程序:
pvNext = (char *)pvData + stSize;
所以无论如何,pvNext都是下一元素的地址。
测试程序:
printf("(char*)&iArray[0] + sizeof(iArray[0]) = %#x \n" , (char*)&iArray[0] + sizeof(iArray[0]) ) ;
printf("&iArray[1] = %#x \n\n" , &iArray[1] ) ;
printf("(char*)&strArray[0] + sizeof(strArray[0]) = %#x \n" , (char*)&strArray[0] + sizeof(strArray[0]) ) ;
printf("&strArray[1] = %#x \n" , &strArray[1] ) ;
结果:
(char*)&iArray[0] + sizeof(iArray[0]) = 0x402004
&iArray[1] = 0x402004
(char*)&strArray[0] + sizeof(strArray[0]) = 0x402028
&strArray[1] = 0x402028
stAmount:数组中包含的元素个数,我们通常使用:sizeof(strArray) / sizeof(strArray[0],即为数组总长度除以元素内存大小,这个结果就是数组元素的个数。
stSize:元素内存大小,sizeof(strArray[0],因为数组内每一个元素的类型相同,所以每个元素的内存大小也就相同。
CompareFun:需要排序的数据类型、升序还是降序。这个函数的原型是:
typedef int (*COMPAREFUN)(const void *pvData1, const void *pvData2);
如果是整型数组需要升序排列,则函数为如程序清单1. 4所示:
程序清单1. 4 整型数组升序
/*******************************************************************************
**函数名称: int_Rise_cmp
**函数功能: 对int进行升序排列
**入口参数: *x:
*y:
**出口参数: ( *(int *)x - *(int *)y )
确定升序
*******************************************************************************/
int int_Rise_cmp(void *x , void *y)
{
return ( *(int *)x - *(int *)y );
}
我们就综合上述对其进行一个整体的分析。假设需排序的数组为:static int iArray[] = {39, 33, 18, 64, 73, 30, 49, 51, 81};pvData是需排序数组的首元素地址,由:
pvThis = pvData;
pvNext = (char *)pvData + stSize;
那么pvThis即为数组首元素的地址,也就是&iArray[0],pvNext为下一个元素的地址,也就是&iArray[1]。接着通过CompareFun(pvThis, pvNext)比较两个元素的大小,进入CompareFun,也就是int_Rise_cmp函数,x即为pvThis,y即为pvNext。这样x即为数组首元素的地址,这里还是void*,我们通过强制转化,将x指向整型,即为(int*)x,再去地址,也就是( *(int *)x,数组首元素,y以此类推。如果( *(int *)x - *(int *)y ) >0,也就是CompareFun(pvThis, pvNext)>0,则交换这两个数据,从而达到从小到大排列的目的。交换完成之后,
pvThis = pvNext;
pvNext = (char *)pvNext + stSize;
这样以此类推。
static int iArray[] = {39, 33, 18, 64, 73, 30, 49, 51, 81};
static char *strArray[] ={"forARM","mzdzkj","c language","shenzhen","china"};
第二个数组值得一提,这是一个指针数组,即为数组中存储的是指针变量。不相信的话可以测试一下。
printf("strArray[0] = %#x \n\n" , strArray[0] ) ;
结果是:
strArray[0] = 0x403000
很显然是指针。上述两个数组经过排序之后的测试结果如程序清单1. 5所示。
程序清单1. 5 测试结果
*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
整型数组数据排序之前:
39 33 18 64 73 30 49 51 81
字符串数组排序之前:
'forARM' 'mzdzkj' 'c language' 'shenzhen' 'china'
整型数组数据升序之后:
18 30 33 39 49 51 64 73 81
整型数组数据降序之后:
81 73 64 51 49 39 33 30 18
字符串数组数据升序之后:
'c language' 'china' 'forARM' 'mzdzkj' 'shenzhen'
字符串数组数据降序之后:
'shenzhen' 'mzdzkj' 'forARM' 'china' 'c language'
2.选择排序
2.1 选择排序算法
一个好的迭代器,只需要修改排序算法,其他的程序都无需修改。其实这里只需要把冒泡排序算法修改为选择排序算法即可。
选择排序算法程序如程序清单2. 1所示。
程序清单2. 1 选择排序函数
/*******************************************************************************
**函数名称: selectSort
**函数功能: 选择排序
**入口参数: *pvData: 需要进行排序的数组
stAmount: 数组中包含的元素个数
stSize: 元素内存大小
CompareFun: 需要排序的数据类型、升序还是降序
**出口参数:
*******************************************************************************/
void selectSort (void *pvData , size_t stAmount, size_t stSize , COMPAREFUN CompareFun)
{
int i , j , k ;
void *pvThis = NULL;
void *pvThat = NULL;
/*
* 冒泡排序
*/
#if 0
printf("pvData = %#x\n" ,pvData ) ;
printf("pvThis = %#x\n" ,pvBegin ) ;
printf("pvThat = %#x\n" ,pvEnd ) ;
#endif
for ( i = 0 ; i < stAmount ; i++ ) {
k = i ;
for ( j = i + 1 ; j < stAmount ; j++) {
pvThis = (char *)pvData + j*stSize;
pvThat = (char *)pvData + k*stSize;
if (CompareFun(pvThis , pvThat ) > 0) {
k = j ;
}
if( k != i ) {
pvThis = (char *)pvData + i*stSize;
pvThat = (char *)pvData + k*stSize;
__swap(pvThis , pvThat , stSize);
}
}
}
}
其实这个选择排序函数和冒泡排序函数只是改动了内部程序,其他地方都没有修改。道理是一样,我就不加说明。
触类旁通的思想真的很重要,当你庖丁解牛对待一个冒泡排序的时候,你会发现其他排序方法也就自然而然会了。
我们看看测试结果:
*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
先测试一些数据,便于我们理解
第一组数据:
sizeof(iArray) = 36
sizeof(iArray[0]) = 4
&iArray = 0x402000
&iArray[0] = 0x402000
(char*)&iArray[0] + sizeof(iArray[0]) = 0x402004
&iArray[1] = 0x402004
&iArray[8] = 0x402020
第二组数据:
sizeof(strArray) = 20
sizeof(strArray[0]) = 4
strArray = 0x402024
&strArray = 0x402024
&strArray[0] = 0x402024
(char*)&strArray[0] + sizeof(strArray[0]) = 0x402028
&strArray[1] = 0x402028
strArray[0] = 0x403000
*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
整型数组数据排序之前:
39 33 18 64 73 30 49 51 81
字符串数组排序之前:
'forARM' 'mzdzkj' 'c language' 'shenzhen' 'china'
整型数组数据升序之后:
18 30 33 39 49 51 64 73 81
整型数组数据降序之后:
81 73 64 51 49 39 33 30 18
字符串数组数据升序之后:
'c language' 'china' 'forARM' 'mzdzkj' 'shenzhen'
字符串数组数据降序之后:
'shenzhen' 'mzdzkj' 'forARM' 'china' 'c language'
请按任意键继续. . .
测试通过!
或许很多学生知道:
int iArray[10];
int i;
iArray[i]是什么,但是我问他i[iArray]是什么?就不知道了。为什么,因为对数组和指针理解不够深入。
iArray[i] = *(iArray+i) = *(i+iArray) = i[iArray],说白了这里就是小学的加法交换律。但是就是因为不理解指针。
马上就要进行14届应届毕业生的招聘会了,笔者就尽自己所能,每天为《攻破C语言笔试与机试难点》写一点吧。
i ++;
}
这个语句没问题,当iNumber与10相等,i则++。
但是,问题的关键在于你是不是每次都会记得是:
iNumber == 10,你会不会有那么一次写成了:
iNumber = 10呢?该死的编译器这个时候不会告诉你这是一个错误。
所以有那么一次,程序是不是就铁定挂了。
为了避免不必要的麻烦:
if (10 == iNumber) {
i ++;
}
这样写,招聘人员一定会眼前一亮。
考虑到有的初学者对第十五节的减法和乘法的疑惑,本节特此将减法和乘法的程序补上。
/******************************************************************************
** 函数名称:BigNumberSub
** 函数功能:大整数的减法运算
** 入口参数:str1:第一个减数
str2:第二个减数
ptr:容纳两数之差的空间首地址
ptrSize:此空间大小
** 出口参数:
******************************************************************************/
int BigNumberSub(const char *str1, const char *str2, char *ptr, int ptrSize)
{
/*
** iStr1Len:存储第一个字符串
** iStr2Len:存储第二个字符串
** iMaxLen :两个字符串中最长的长度
** i、j :循环
** iTemp :当前位暂存位
** iBorrow :进位标志位
*/
int iStr1Len , iStr2Len , iMaxLen , i , j , iTemp = 0 , iBorrow = 0 ;
char character1 , character2 ;
/* 测量两个字符串的长度 */
iStr1Len = strlen(str1) ;
iStr2Len = strlen(str2) ;
/* 将ptr存储区域的数据全部清零 */
memset(ptr, 0, ptrSize) ;
/* 如果被减数小于减数 */
if ( iStr1Len < iStr2Len ) {
return 0 ;
}
/* 从高位到低位逐位相减 */
for ( i = 0 ; i < iStr1Len ; i++ ) {
character1 = str1[iStr1Len - 1 - i] ;
character2 = (iStr2Len - 1 - i) < 0 ? '0' : str2[iStr2Len - 1 - i] ;
/* 如果有哪一位不是数字,则退出运算 */
if ( (!isdigit(character1)) || (!isdigit(character2)) ) {
return 0 ;
}
/* 当前位相减之后的暂存值 */
iTemp = (character1 - '0') + 10 - iBorrow - (character2 - '0') ;
assert(i < ptrSize) ;
/* 将当前相减位的值转换为字符存入结果 */
ptr = iTemp % 10 + '0' ;
/* 借位位的值 */
iBorrow = 1 - iTemp / 10 ;
}
assert(i < ptrSize) ;
ptr = '\0' ;
/* 将字符串从高位依次输出 */
for ( j = 0 ; j < i-- ; j++) {
char cTemp = ptr[j] ;
ptr[j] = ptr ;
ptr = cTemp ;
}
return 1 ;
}
/******************************************************************************
** 函数名称:BigNumberMul
** 函数功能:大整数的乘法运算
** 入口参数:str1:第一个乘数
str2:第二个乘数
ptr:容纳两数之积的空间首地址
ptrSize:此空间大小
** 出口参数:
******************************************************************************/
int BigNumberMul(const char *str1, const char *str2, char *ptr, int ptrSize)
{
/*
** iStr1Len:存储第一个字符串
** iStr2Len:存储第二个字符串
** iMaxLen :两个字符串中最长的长度
** i、j :循环
** iCarry :进位标志位
*/
int iStr1Len , iStr2Len , iMaxLen , i , j , iCarry = 0 ;
/* 测量两个字符串长度 */
iStr1Len = strlen(str1) ;
iStr2Len = strlen(str2) ;
/* 将ptr存储区域的数据全部清零 */
memset(ptr, 0, ptrSize) ;
/* 用数据一的低位到高位逐位和数据二相乘 */
for ( i = 0 ; i < iStr1Len ; i++ ) {
/* 如果字符串1中有不是数字,则退出 */
if ( (!isdigit(str1[iStr1Len - 1 - i])) ) {
return 0 ;
}
/* 从低位向高位逐位相乘 */
for ( j = 0 ; j < iStr2Len ; j++ ) {
/* 如果字符串2中有不是数字,则退出 */
if ( (!isdigit(str2[iStr2Len - 1 - i])) ) {
return 0 ;
}
/* 确保相乘之积能存储下 */
assert((i+j) < ptrSize) ;
/* 逐位相乘 */
iCarry += (str1[iStr1Len-1-i]-'0')*(str2[iStr2Len-1-i]-'0')+ptr[i+j] ;
ptr[i+j] = iCarry % 10 ;
iCarry /= 10 ;
}
/* 处理进位 */
for ( ; iCarry != 0 ; j++ ) {
assert((i+j) < ptrSize) ;
iCarry += ptr[i+j] ;
ptr[i+j] = iCarry % 10 ;
iCarry /= 10 ;
}
}
i = ptr[iStr1Len+iStr2Len-1] ? (iStr1Len+iStr2Len) : (iStr1Len+iStr2Len-1) ;
for ( j = 0 ; j < i ; j++ ) {
ptr[j] += '0' ;
}
/* 将结果从高位到低位输出 */
for ( j = 0 ; j < --i ; j++ ) {
char cTemp = ptr[j] ;
ptr[j] = ptr ;
ptr = cTemp ;
}
return 1 ;
}
其实大整数的加减乘除法很多公司都喜欢用来作为机试题目,作为今年找工作的亲们得好好看一下。其实笔试和机试都只是一种考核大家能力的形式,或许或多或少可能不能完全衡量一个人,但是企业要在几天内决定是否录用你,考试还是最直接的办法。你要是能通过你的三寸不烂之舌把HR搞定,我朱兆祺也佩服你。或许哪天我会邀请你来做我的首席销售员。呵呵。
C语言程序的经典与否很大在于算法是否经典,这一节开始朱兆祺带领大家学习C语言算法篇。
就拿二分查找下手。
// text.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
int BinarySeach(int *iArray, int key, int n)
{
int iLow = 0 ;
int iHigh = n - 1;
int iMid;
//大家想想这是循环呢?还是递归呢。
while (iLow <= iHigh) {
iMid = (iHigh + iLow)/2;
if (iArray[iMid] > key) {
iHigh = iMid - 1 ;
} else if (iArray[iMid] < key) {
iLow = iMid + 1 ;
} else{
return iMid ;
}
}
}
//测试程序
int main(int argc, char* argv[])
{
int a[10]={1,2,3,4,5,6,7,8,9,10};
printf("%d\n" , BinarySeach(a,6,10));
return 0;
}
递归调用就是函数对自身的调用,但是一定要慎重使用,递归涉及到栈是否会溢出等问题,还有就是算法是否合适,并非说任何时候都是适合的。
我记得谭浩强的《C……》中用递归算法来解决阶乘的问题,我就疑惑了,使用循环解决岂不是更简单明了。
1.1 创建链表
1.1.1 获取链表第一个节点位置
1.1.2 获取链表最后一个节点的下一个节点位置
1.1.3 获取链表的元素个数
1.1.4 由位置获取数据的值
1.1 插入节点1.1.1 将数据插入到指定位置
1.1.1 将数据插入到链表头成为第一个节点
1.1.2 将数据插入到链表尾成为最后一个节点
1.2 删除节点1.2.1 删除指定位置的节点
图3. 11 free(pos)
|
free(pos) ;
1.1.1 删除链表头节点后的节点(即第一个节点)
1.1.2 删除链表的尾节点
1.2 按值搜索位置算法
1.1 遍历算法
1.2 销毁链表
假设处理器不能实现除法运算,通过加减法高效地求出两个无符号整数的商和余数。 事实上,在微处理器上,所有的四则运算都会转化为加法,但是除法运算会消耗掉微处理器大量的时间,因此我们设计一个算法,只通过加减法求出两个无符号整数的商和余数。这个题目广州致远电子有限公司曾经用来作为招聘软件工程师的机试题目。说起周立功,首先确实得感谢他,从大一暑假开始就给我提供夏令营机会在广州致远电子有限公司实习,将软件功底一次次提升,去年7月份开始更和致远签下合同,开始正式实习之路。但是11月份,由于个人想法,依然离开了广州致远。扯远了,拉回来。
算法二:使用递归算法实现。除数(iDivisor) =除数(iDivisor)*2。因此,商(iCompany)=商(iCompany)*2。如果余数大于除数,则商(iCompany)= 商(iCompany)+1,余数(iRemainder)=余数(iRemainder)- 除数(iDivisor)。假设被除数(iDividend) =100,除数(iDivisor)=3,则递归算法求商和余数过程。
很显然,算法一效率太低,不可取,因此选择算法二。
采用算法二,就涉及到返回值的传递,方法有四种:
图 2.1 程序流程图
|
方法二:结构体,结构体确实可以保存很多变量,并且传回来也是没有问题的。但是结构体所占内存较大,不提倡使用。
对比上面四种方法,从程序健壮性、可移植性、安全性等诸多方面考虑采取方法三或方法四。
}
攻破C语言到现在是不是该结束了呢,非也,前面二十一节只是给大家填充下你们在大学遗漏的C语言基本知识。对,没错,是基本知识,或许你看前面的程序或者有难度,但是我可以告诉你,这些本应都是在大学应该完成的知识体系,只是因为大学期间的种种原因,没有去完成。
好了,基本知识体系差不多补充完整了,从这节开始,我们上实战了。
要求:写一个控制台程序,该程序实现输入一个表达式能够根据表达式计算出相应的结果。
例如:例如:输入字符串"1+2*(3-4)/5=",输出结果:0.6
要求:
基本要求:能够对+ - * / 进行运算,能够对优先级进行运算和括号匹配运算。
扩展要求:
1. 支持多数据类型输入,如: 0x12F(十六进制)、 12FH(十六进制)、 234O(八进制) 、‘F’(字符) 、0000 1111B(二进制)
2. 支持未知数赋值运算 如: x=3 则 3X+5=14
3. 支持常用函数运算 如: log、 ln、 sin、 sqrt等;
4. 支持大数据计算;
5. 支持自定义运算符。
这个题目涉及的东西很多很多,在往后待我慢慢给大家分析。暂且先将这个帖子闲置几天,看看有没有网友对此有想法,如果你想学习下,OK、联系我,我会给你提供思路和帮助。
《朱兆祺教你如何攻破C语言学习、笔试与机试的难点》后续更经常,朱兆祺用4年C语言经验、2年项目经验告诉你如何写出一个漂亮的算法写出一手漂亮的代码,敬请关注。
在二十二节我就说了,表达式计算器所涉及的知识点非常多,我先给大家罗列下:
1.动态双向链表(这是我的算法,或许你可以试试数组,但是这两者的优劣在何处?我希望你能明白),表达式的校对
2.中缀表达式转化为后缀表达式
3.后缀表达式的计算
4.文件读写
5.不同进制的转换
6.大整数算法
我从大方向大致列举出这六点,现在我们就一项一项进行搞定。
我们首先建立一个动态双向链表:
/* 建立一个头结点*/
Formula_head = new node;
init_node(Formula_head);
Formula_head->ch = getchar();
Formula_follow = Formula_head;
/* 从用户读取输入*/
for(;;){
newnode = new node; /* 新增加一个节点来存储输入*/
init_node(newnode); /* 初始化节点数据 */
while(1){ /* 获取输入数据,并且删除空格*/
newnode->ch = getchar();
//cin >> newnode->ch ;
if(' ' != newnode->ch ){
//print_link_data(Formula_follow);
break;
}
}
/* 结束输入*/
if('\n' == newnode->ch){
break;
}
/* 加入节点*/
Formula_follow = add_node(Formula_follow, newnode);
}
读取表达式之后,需要对表达式进行校对,处理未知数和防止输入不规则表达式(错误表达式):
/* 检查是否是未知数算式*/
if( check_unknow(Formula_head) ){
x_head = Formula_head;
Formula_head = NULL;
X_flag = 1;
continue;
}
/*如果有未知数的处理*/
if(X_flag){
Formula_head = add_x_in_link(Formula_head,x_head);
}
/*检查错误*/
if(check(Formula_head)){
continue;
}
/***************************************************
** 函数名称:check
** 函数功能:扫描链表,检查表达式是否错误
** 入口参数:node *head
** 出口参数:
***************************************************/
int check(node *head)
{
int brackets = 0;
for(; head; head=head->next){
/*连续出现2个运算符,报错*/
if(('+'==head->ch) || ('-'==head->ch) ||
('*'==head->ch) || ('/'==head->ch)){
if(('+'==head->next->ch) || ('-'==head->next->ch) ||
('*'==head->next->ch) || ('/'==head->next->ch)){
cout<<"erro: Consecutive two operators!"<<endl;
return 1;
}
}
/* 括号不匹配,报错*/
if('(' == head->ch){
brackets++;
}
if(')' == head->ch){
brackets--;
if(brackets<0){
cout<<"erro: brackets is not right,please check it out!"<<endl;
return 1;
}
}
}
/* 括号不匹配*/
if(0 != brackets){
cout<<"erro: brackets is not right,please check it out!"<<endl;
return 1;
}
/*没错返回0*/
return 0 ;
}
/***************************************************
** 函数名称:check_unknow
** 函数功能:检查是否为未知数算式
** 入口参数:node *head
** 出口参数:
***************************************************/
int check_unknow(node *head)
{
if(('x'==head->ch) && ('='==head->next->ch) && (NULL!=head->next->next)){
return 1 ;
}
return 0;
}
这里开始,我们就不再是小试牛刀了,而是将数据结构真正用于实际项目中,达到算法最优、代码最优的效果。
最近事情比较多,有几天没有更新了,朱兆祺今天抽点时间,无论如何也得更新下。
承接上一节所讲,这一节我们应该到了中缀表达式转化为后缀表达式。这个数据结构在计算器表达式的项目中是非常经典的。这里我就再啰嗦几句,中缀表达式也叫做中缀记法,如1+2*3便是,因为操作符在中间嘛,所以就叫做中缀表达式。那么什么叫做后缀表达式,举一反三,即为操作数在后面嘛,那么即是:123*+。后缀表达式也叫做逆波兰表达式。
中缀表达式转化为后缀表达式,栈的用武之地来了,说到栈,第一个词语就是FILO,先进后出。中缀表达式转化为后缀表达式也正是用了这一个特点。
/***************************************************
** 函数名称:*mid_to_bk
** 函数功能:中缀转换成后缀表达式
** 入口参数:node *head
** 出口参数:
***************************************************/
node *mid_to_bk(node *head)
{
node *mid = Separate_str(head); /* 处理后的中缀表达式链表*/
node *bk = NULL; /* 后缀表达式头指针*/
node *ptr = bk;
node *oper = NULL; /* 符号堆栈 */
node *newnode = NULL;
node *code_node = NULL; /* 用来记录,保存地址 */
/* 开始转换*/
for(; mid; mid=mid->next){
/* 将节点数据拷贝到新节点去*/
newnode = new node;
init_node(newnode);
copy_node_data(newnode,mid);
/* 如果遇到数字就直接入栈*/
if(NUM == newnode->type){
/*加入链表*/
if(NULL == bk){
ptr = bk = add_node(ptr,newnode);
} else{
ptr = add_node(ptr,newnode);
}
}
/* 如果遇到特殊表达式*/
else if(SP_OPERATOR == newnode->type){
/* 如果前一个运算符也是特殊运算符就要出栈 */
while(oper){
if(SP_OPERATOR == oper->type){
code_node = oper->befor; /* 将这个节点换到后缀表达式里面*/
ptr = add_node(ptr,oper);
oper = code_node;
} else break;
}
oper = add_node(oper,newnode);
}
/*如果遇到普通运算符*/
else if(OPERATOR == newnode->type){
/*'('直接进栈*/
if('(' == newnode->ch){
oper = add_node(oper,newnode);
}
/*')'直接退栈到')'*/
else if(')' == newnode->ch){
while(oper){
if('(' == oper->ch)break;/* 遇到'('退栈结束*/
code_node = oper->befor; /* 将这个节点换到后缀表达式里面*/
ptr = add_node(ptr,oper);
oper = code_node;
}
oper = del_node(oper); /* 删除掉'('符号*/
}
/* 遇到+-全部退栈,直到遇到'(',或者退完为止*/
else if(('+' == newnode->ch) || ('-' == newnode->ch)){
while(oper){
if('(' == oper->ch)break; /* 遇到'('退栈结束*/
code_node = oper->befor; /* 将这个节点换到后缀表达式里面*/
ptr = add_node(ptr,oper);
oper = code_node;
}
oper = add_node(oper,newnode);
}
/* 遇到/* 把特殊运算符全部退栈处理*/
else if(('*' == newnode->ch) || ('/' == newnode->ch)){
while(oper){
if('(' == oper->ch)break; /* 遇到'('和特殊运算符截止*/
if(('+' == oper->ch) || ('-' == oper->ch))break;
code_node = oper->befor; /* 将这个节点换到后缀表达式里面*/
ptr = add_node(ptr,oper);
oper = code_node;
}
oper = add_node(oper,newnode);
}
/*
* 下面这段程序是调试使用
*/
#if 0
cout<<"mid: ";
print_link_data(test);
getchar();
#endif
}
}
/* 把最后留在堆栈里面的东西退出来*/
while(oper){
code_node = oper->befor; /* 将这个节点换到后缀表达式里面*/
ptr = add_node(ptr,oper);
oper = code_node;
}
ptr->next = NULL;
/*
* 下面这段程序是调试使用
*/
#if 0
cout<<"mid: ";
print_link_data(bk);
#endif
return bk;
}
C语言并不难学,数据结构也并不难。关键在于会不会花时间去学,会不会坚持下去。最近很多网友问我,学了一段时间就想放弃,我只想说4个字:剩者为王。
承接上一节,我们实现了中缀表达式转化为逆波兰表达式,这节,我们要做的肯定就是怎么样将逆波兰表达式计算出来。
其实这个计算过程是很简单了,如下:
/***************************************************
** 函数名称:calculate
** 函数功能:对后缀表达式进行计算处理
** 入口参数:node *formula
** 出口参数:result[0]
***************************************************/
double calculate(node *formula)
{
double result[200];
int i=0;
for(; formula; formula = formula->next){
/*数据再次进堆栈*/
if(NUM == formula->type){
result = formula->num;
i++;
}
//由于前面进行了中缀表达式转化为逆波兰表达式,因此为这里的计算奠定了十足的运算基础。
/*遇到普通符号就直接进行判断并且计算*/
else if(OPERATOR == formula->type){
/*加法计算*/
if('+' == formula->ch){
i--;
result[i-1] = result[i-1]+result;
}
/*减法计算*/
if('-' == formula->ch){
i--;
result[i-1] = result[i-1]-result;
}
/*乘法计算*/
if('*' == formula->ch){
i--;
result[i-1] = result[i-1]*result;
}
/*除法计算*/
if('/' == formula->ch){
i--;
/* 如果除数是0的话提示错误*/
if(0 == result){
cout<<"erro: the divvisor equal zero!Please check is out!"<<endl;
exit(1);
} else{
result[i-1] = result[i-1]/result;
}
}
}
/*遇到特殊符号*/
else{
/*sin*/
if(!strcmp(formula->str,"sin")){ //进行符号匹配,如果是sin运算符,进行项目的运算。这里读者其实可以进行改进
result[i-1] = sin(result[i-1]);
}
/* cos*/
else if(!strcmp(formula->str,"cos")){
result[i-1] = cos(result[i-1]);
}
/* tan*/
else if(!strcmp(formula->str,"tan")){
result[i-1] = tan(result[i-1]);
}
/* log*/
else if(!strcmp(formula->str,"log")){
result[i-1] = log(result[i-1]);
}
/*sqrt*/
else if(!strcmp(formula->str,"sqrt")){
result[i-1] = sqrt(result[i-1]);
}
/*help*/
else if(!strcmp(formula->str,"help")){
r_file() ;
} else{
cout<<"have no this OPERATOR,if you want add it,please add it upon, thank you!\n"<<endl;
exit(1);
}
}
}
return result[0];
}
有些东西,看似很难,那是因为你没有突破心理的障碍,当你全身心投入去实践的时候,你会发现,一切都是那么简单并且有趣。在嵌入式道路上,朱兆祺与你们同行。朱兆祺在最近两年内,一定会完成从C语言到单片机到ARM到嵌入式的整套完整的学习资料,并且全部开源,为你们在嵌入式道路杀出一条光明大道。
// text.cpp : 定义控制台应用程序的入口点。
/***********************************************************************
** 程序名称 : 交换数据
** 程序描述 : 通过交换a,b中的元素,使[序列a元素的和]与[序列b无素的和]之间的差最小
************************************************************************/
#include "stdafx.h"
/*******************************************************************
** 函数名称 : void speed(int *iNum , int max)
** 函数功能 : 应用快速排序法排列数组顺序(从小到大)
** 入口参数 : int *iNum , int max //想想这两个参数
** 出口参数 : 无
********************************************************************/
void speed(int *iNum , int max)
{
int j , k ;
/* 这两层循环什么意思,还有什么和这个是相似的 */
for ( j = 0 ; j < max ; j++ )
{
for ( k = j + 1 ; k < max ; k++ )
{
if ( iNum[j] > iNum[k] )
{
/* 想想还有什么办法 */
iNum[j] ^= iNum[k] ^= iNum[j] ^= iNum[k] ;
}
}
}
}
/*******************************************************************
** 函数名称 : int sumall( int *all , int max )
** 函数功能 : 对序列求和
** 入口参数 : int *all , int max
** 出口参数 : sum
*******************************************************************/
int sumall( int *all , int max )
{
int i , sum = 0 ;
for( i=0 ; i < max ; i++ )
{
sum += all ;
}
return sum ;
//printf("和为:%d\n" , sum );
}
/*******************************************************************
** 函数名称 : void change( int *aNum , int *bNum , int max )
** 函数功能 : 对AB序列进行交换数据
** 入口参数 : int *aNum , int *bNum , int max
** 出口参数 : 无
*******************************************************************/
void change( int *aNum , int *bNum , int max )
{
int i , j , n , m , k ;
for ( i = 0 ; i < max ; i++ )
{
/* 认真看下这个if语句 */
if( ( (sumall(aNum,max) < sumall(bNum,max))&&(aNum<bNum) )||
( (sumall(aNum,max) > sumall(bNum,max))&&(aNum>bNum) ) )
{
aNum ^= bNum ^= aNum ^= bNum ;
speed(aNum,max) ;
speed(bNum,max) ;
}
}
printf( "*************************************\n" );
printf( "A序列调整之后:" ) ;
for ( n = 0 ; n < max ; n++ )
{
printf( "%d, " ,aNum[n]) ;
}
printf( "\n" );
printf( "B序列调整之后:" ) ;
for ( m = 0 ; m < max ; m++ )
{
printf( "%d, " ,bNum[m]) ;
}
printf( "\n" );
printf( "*************************************\n" ) ;
printf( "A序列调整之后的和:%d\n" , sumall(aNum,max) ) ;
printf( "B序列调整之后的和:%d\n" , sumall(bNum,max) ) ;
}
/***********************************************************************
** 函数名称 : 主函数
** 函数功能 : 输入和输出数据
** 入口参数 : int argc, char* argv[]
** 出口参数 : return
************************************************************************/
int main(int argc, char* argv[])
{
int i , j , n , m ;
int A[100]={} ;
int B[100]={} ;
int max ;
printf( "请输入你要输入的最大个数:Max=" ) ;
scanf( "%d" , &max );
printf( "请输入A序列数据 :\n" ) ;
for ( i = 0 ; i < max ; i++ )
{
scanf( "%d",&A ) ;
}
printf( "请输入B序列数据 :\n" ) ;
for ( j = 0 ; j < max ; j++ )
{
scanf( "%d",&B[j] ) ;
}
speed( A , max );
speed( B , max );
printf( "*************************************\n" );
printf( "排序之后的A序列:" ) ;
for ( n = 0 ; n < max ; n++ )
{
printf( "%d, " ,A[n] ) ;
}
printf( "\n" );
printf( "排序之后的B序列:" ) ;
for ( m = 0 ; m < max ; m++ )
{
printf( "%d, " ,B[m] ) ;
}
printf( "\n" );
printf( "*************************************\n" ) ;
change( A , B , max ) ;
return 0;
}