顺序线性表c语言实现顺序问题

c语言关于n个数从大到小的排序问题
c语言关于n个数从大到小的排序问题
10-01-06 &匿名提问
教了多年《C程序设计》课程,大多学生觉的这门课程难学。其实,按照我们现在的教学大纲和教学要求,只要同学们掌握一些方法,克服心理上畏难、不轻言放弃,是完全可以学好的。 《C程序设计》的内容很丰富,按照我们现在的教学大纲,教学的主要内容是基础知识、四种结构的的程序设计、函数与数组的应用和一些简单的算法。在学习时,同学们应该把主要精力放在这些部分,通过实践(练习和上机调试等熟练掌握。当然,在初学C语言时,可能会遇到有些问题理解不透,或者表达方式与以往数学学习中不同(如运算符等),这就要求不气馁,不明白的地方多问多想,鼓足勇气进行学习,待学完后面的章节知识,前面的问题也就迎刃而解了,这一方面我感觉是我们同学最欠缺,大多学不好的就是因为一开始遇到困难就放弃,曾经和好多同学谈他的问题,回答是听不懂、不想听、放弃这样三个过程,我反问,这节课你听过课吗?回答又是没有,根本就没听过课,怎么说自己听不懂呢?相应的根本就没学习,又谈何学的好? 学习C语言始终要记住“曙光在前头”和“千金难买回头看”,“千金难买回头看”是学习知识的重要方法,就是说,学习后面的知识,不要忘了回头弄清遗留下的问题和加深理解前面的知识,这是我们学生最不易做到的,然而却又是最重要的。比如:在C语言中最典型的是关于结构化程序设计构思,不管是那种教材,一开始就强调这种方法,这时也许你不能充分体会,但是学到函数时,再回头来仔细体会,温故知新,理解它就没有那么难了。学习C语言就是要经过几个反复,才能前后贯穿,积累应该掌握的C知识。 那么,我们如何学好《C程序设计》呢? 一.学好C语言的运算符和运算顺序 这是学好《C程序设计》的基础,C语言的运算非常灵活,功能十分丰富,运算种类远多于其它程序设计语言。在表达式方面较其它程序语言更为简洁,如自加、自减、逗号运算和三目运算使表达式更为简单,但初学者往往会觉的这种表达式难读,关键原因就是对运算符和运算顺序理解不透不全。当多种不同运算组成一个运算表达式,即一个运算式中出现多种运算符时,运算的优先顺序和结合规则显得十分重要。在学习中,只要我们对此合理进行分类,找出它们与我们在数学中所学到运算之间的不同点之后,记住这些运算也就不困难了,有些运算符在理解后更会牢记心中,将来用起来得心应手,而有些可暂时放弃不记,等用到时再记不迟。 先要明确运算符按优先级不同分类,《C程序设计》运算符可分为15种优先级,从高到低,优先级为1 ~ 15,除第2、3级和第14级为从右至左结合外,其它都是从左至右结合,它决定同级运算符的运算顺序。下面我们通过几个例子来说明: (1) 5*8/4%10 这个表达式中出现3种运算符,是同级运算符,运算顺序按从左至右结合,因此先计算5 *8=40,然后被4除,结果为10,最后是%(求余数)运算,所以表达式的最终结果为10%10 = 0; (2)a = 3;b = 5;c =++ a* b ;d =a + +* b; 对于c=++a*b来说,按表中所列顺序,+ +先执行,*后执行,所以+ + a执行后,a的值为4,由于+ +为前置运算,所以a的值4参与运算,C的值计算式为4*5=20而不是3*5=15了;而对于d=a++*b来说,由于a + +为后置运算,所以a值为4参与运算,使得d的值仍为20,而a参与运算后其值加1,值为5。 这个例子执行后,a的值为5,b的值为5,c的值为20,d的值也是20; (3)(a = 3,b = 5,b+ = a,c = b* 5) 例子中的“,”是逗号结合运算,上式称为逗号表达式,自左向右结合,最后一个表达式的结果值就是逗号表达式的结果,所以上面的逗号表达式结果为40,a的值为3,b的值为8,c的值为40。 (4)a=5;b=6;c=a&b?a:b; 例中的a&b?a:b是一个三目运算,它的功能是先做关系运算a&b部分,若结果为真,则取问号后a的值,否则取冒号后b的值,因此c的值应该为6,这个运算可以用来代替if…else…语句的简单应用。 二.学好C语言的四种程序结构 (1)顺序结构 顺序结构的程序设计是最简单的,只要按照解决问题的顺序写出相应的语句就行,它的执行顺序是自上而下,依次执行。 例如;a = 3,b = 5,现交换a,b的值,这个问题就好象交换两个杯子水,这当然要用到第三个杯子,假如第三个杯子是c,那么正确的程序为: c = a; a = b; b = c; 执行结果是a = 5,b = c = 3如果改变其顺序,写成:a = b; c = a; b = c; 则执行结果就变成a = b = c = 5,不能达到预期的目的,初学者最容易犯这种错误。 顺序结构可以独立使用构成一个简单的完整程序,常见的输入、计算,输出三步曲的程序就是顺序结构,例如计算圆的面积,其程序的语句顺序就是输入圆的半径r,计算s = 3.14159*r*r,输出圆的面积s。不过大多数情况下顺序结构都是作为程序的一部分,与其它结构一起构成一个复杂的程序,例如分支结构中的复合语句、循环结构中的循环体等。 (2) 分支结构 顺序结构的程序虽然能解决计算、输出等问题,但不能做判断再选择。对于要先做判断再选择的问题就要使用分支结构。分支结构的执行是依据一定的条件选择执行路径,而不是严格按照语句出现的物理顺序。分支结构的程序设计方法的关键在于构造合适的分支条件和分析程序流程,根据不同的程序流程选
请登录后再发表评论!
我回,在书的P120
请登录后再发表评论!请教一个C语言顺序点的问题。。。。 - 开源中国社区
当前访客身份:游客 [
当前位置:
各位大神,小生才疏学浅请教一个c语言顺序点问题,代码如下:
#include “stdio.h”
void f(int i,int j)
printf(&%d ,%d
int main()
根据顺序点的知识,为什么k传入的值是2,而k++传入的值是1?小生编译器是gcc,是因为编译器不同,顺序就运算顺序就不同吗?
共有8个答案
<span class="a_vote_num" id="a_vote_num_
参数入栈顺序是和具体编译器实现相关的。
C方式参数入栈顺序(从右至左)的好处就是可以动态变化参数个数。通过栈堆分析可知,自左向右的入栈方式,最前面的参数被压在栈底。除非知道参数个数,否则是无法通过栈指针的相对位移求得最左边的参数。这样就变成了左边参数的个数不确定,正好和动态参数个数的方向相反。
因此,C语言函数参数采用自右向左的入栈顺序,主要原因是为了支持可变长参数形式。
--- 共有 1 条评论 ---
可以在深入解释下:动态变化参数个数
(2年前)&nbsp&
<span class="a_vote_num" id="a_vote_num_
k++是指先运行表达式,再加1; k先赋值给方法,然后加1,然后k值变成了2,然后k再赋值给函数,最后调用函数,所以有先2后1的输出。 核心细节 可能 是表达式计算用的先进后出
<span class="a_vote_num" id="a_vote_num_
仅供参考!不一定对喃!
<span class="a_vote_num" id="a_vote_num_
从右向左,解析并编译。哈。理解这个就ok了。另外给楼主个提示,我学习和开发c语言的程序,也快20年了。类似你这样的问题,从来不纠结。。对于++i,i++,我一向保持,仅发生在通用实现方式,或独立一条语句中。哈,所谓通用实现方式,例如:
for(i = 0; i & 10 ;i++)
*p1++= *p2++;
其他情况几乎都是独立一个语句折腾,省得自己或别人对代码还要反复核对。。。累不累?反正我是累了。
<span class="a_vote_num" id="a_vote_num_
根据你的描述是,函数f()的解析是从右到左的,而i++的i是用了再加,所以i++传入的值是1,自加后i的值就是2了。
<span class="a_vote_num" id="a_vote_num_
引用来自“中山野鬼”的评论 从右向左,解析并编译。哈。理解这个就ok了。另外给楼主个提示,我学习和开发c语言的程序,也快20年了。类似你这样的问题,从来不纠结。。对于++i,i++,我一向保持,仅发生在通用实现方式,或独立一条语句中。哈,所谓通用实现方式,例如:
for(i = 0; i & 10 ;i++)
*p1++= *p2++;
其他情况几乎都是独立一个语句折腾,省得自己或别人对代码还要反复核对。。。累不累?反正我是累了。 我也不是纠结这个问题,我就想搞明白这是为什么,至于代码我也不会写成那样,于人于己都不好。。。。
<span class="a_vote_num" id="a_vote_num_
这个就是sequence point和side effect问题. func(f1(),g1());&
上面的表达式到底先执行那个函数 顺序是不确定的. 是Unspecified. &
函数调用会产生一个side effect, 而调用函数之前是一个Sequence Point. 所以可以确定func()将会在f1(), g1()之后执行. 同时两个Sequence Point之间的side effect执行顺序是unspecified的. 所以先执行f1()还是先执行g1() 是不确定的.&
这个用clang去编译得到的编译器警告就说的很清楚了,&
int func(int a,int b)
int main(void)
i = ++i + 1;
func(i,i++);
&&& clang t.c
t.c:8:9: warning: multiple unsequenced modifications to 'i' [-Wunsequenced]
i = ++i + 1;
t.c:9:13: warning: unsequenced modification and access to 'i' [-Wunsequenced]
func(i,i++);
2 warnings generated.
--- 共有 1 条评论 ---
关于顺序点, side effect的定义和说明在ISO/IEC 的5.1.2.3节和Annex C
(2年前)&nbsp&
<span class="a_vote_num" id="a_vote_num_
引用来自“中山野鬼”的评论 从右向左,解析并编译。哈。理解这个就ok了。另外给楼主个提示,我学习和开发c语言的程序,也快20年了。类似你这样的问题,从来不纠结。。对于++i,i++,我一向保持,仅发生在通用实现方式,或独立一条语句中。哈,所谓通用实现方式,例如:
for(i = 0; i & 10 ;i++)
*p1++= *p2++;
其他情况几乎都是独立一个语句折腾,省得自己或别人对代码还要反复核对。。。累不累?反正我是累了。 引用来自“mcu小生”的评论我也不是纠结这个问题,我就想搞明白这是为什么,至于代码我也不会写成那样,于人于己都不好。。。。有些事情,只有你真的做更深层的东西时,才能真正理解。否则就是今天会了。明年也忘了。对自己工作也没有帮助。哈。
更多开发者职位上
有什么技术问题吗?
mcu小生的其它问题
类似的话题C语言中函数参数入栈的顺序 - Fangzhen - 博客园
随笔 - 367, 文章 - 174, 评论 - 109, 引用 - 0
先通过一个小程序来看一看:
void foo(int x, int y, int z)
printf("x = %d at [%X]n", x, &x);
printf("y = %d at [%X]n", y, &y);
printf("z = %d at [%X]n", z, &z);
int main(int argc, char *argv[])
foo(100, 200, 300);
运行结果:
x = 100 at [BFE28760]
y = 200 at [BFE28764]
z = 300 at [BFE28768]
&C程序栈底为高地址,栈顶为低地址,因此上面的实例可以说明函数参数入栈顺序的确是从右至左的。可到底为什么呢?查了一直些文献得知,参数入栈顺序是和具体编译器实现相关的。比如,Pascal语言中参数就是从左到右入栈的,有些语言中还可以通过修饰符进行指定,如Visual C++.即然两种方式都可以,为什么C语言要选择从右至左呢?
进一步发现,Pascal语言不支持可变长参数,而C语言支持这种特色,正是这个原因使得C语言函数参数入栈顺序为从右至左。具体原因为:C方式参数入栈顺序(从右至左)的好处就是可以动态变化参数个数。通过栈堆分析可知,自左向右的入栈方式,最前面的参数被压在栈底。除非知道参数个数,否则是无法通过栈指针的相对位移求得最左边的参数。这样就变成了左边参数的个数不确定,正好和动态参数个数的方向相反。
因此,C语言函数参数采用自右向左的入栈顺序,主要原因是为了支持可变长参数形式。换句话说,如果不支持这个特色,C语言完全和Pascal一样,采用自左向右的参数入栈方式。
&这儿其实还涉及到C语言中调用约定所采用的方式,下面简单的介绍一下:
__stdcall与C调用约定(__cdecl)的区别
&C调用约定在返回前,要作一次堆栈平衡,也就是参数入栈了多少字节,就要弹出来多少字节.这样很安全.
有一点需要注意:stdcall调用约定如果采用了不定参数,即VARARG的话,则和C调用约定一样,要由调用者来作堆栈平衡.
(1)_stdcall是Pascal方式清理C方式压栈,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。 int f(void *p) --&& _f@4(在外部汇编语言里可以用这个名字引用这个函数)
在WIN32 API中,只有少数几个函数,如wspintf函数是采用C调用约定,其他都是stdcall
(2)C调用约定(即用__cdecl关键字说明)(The C default calling convention)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数vararg的函数(如printf)只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。 _cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。
(3)__fastcall调用的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同。__fastcall方式的函数采用寄存器传递参数,VC将函数编译后会在函数名前面加上"@"前缀,在函数名后加上"@"和参数的字节数。
(4)thiscall仅仅应用于"C++"成员函数。this指针存放于CX/ECX寄存器中,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。
(5)naked call。 当采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。
  (这些代码称作 prolog and epilog code,一般,ebp,esp的保存是必须的).
  但是naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec共同使用。
  关键字 __stdcall、__cdecl和__fastcall可以直接加在要输出的函数前。它们对应的命令行参数分别为/Gz、/Gd和/Gr。缺省状态为/Gd,即__cdecl。
  要完全模仿PASCAL调用约定首先必须使用__stdcall调用约定,至于函数名修饰约定,可以通过其它方法模仿。还有一个值得一提的是WINAPI宏,Windows.h支持该宏,它可以将出函数翻译成适当的调用约定,在WIN32中,它被定义为__stdcall。使用WINAPI宏可以创建自己的APIs。
综上,其实只有PASCAL调用约定的从左到右入栈的.而且PASCAL不能使用不定参数个数,其参数个数是一定的。
简单总结一下上面的几个调用方式:
调用约定堆栈清除参数传递__cdecl调用者从右到左,通过堆栈传递__stdcall函数体从右到左,通过堆栈传递__fastcall
函数体从右到左,优先使用寄存器(ECX,EDX),然后使用堆栈thiscall函数体this指针默认通过ECX传递,其他参数从右到左入栈&&&3993人阅读
C/C++学习(15)
1)“冒泡法1”&
&& &其原理为从a[0]开始,依次将其和后面的元素比较,若a[0]&a[i],则交换它们,一直比较到a[n]。
同理对a[1],a[2],...a[n-1]处理,即完成排序。&
void bubble(int *a,int n)
for(i=0;i&n-1;i++)
for(j=i+1;j&n;j++) /*注意循环的上下限*/
if(a[i]&a[j])
temp=a[i];
a[i]=a[j];
2)“冒泡法2”&
冒泡法还有第二种形式,或者叫沉底法也行:
每相邻的两个数进行比较,如果发现b[j]&b[j&#43;1]
void bubble(int *b,int n)
for(i=0;i&n-1;i++)
for(j=0;j&n-i-1;j++)
if(b[j]&b[j+1])
temp = b[j];
b[j] = b[j+1];
冒泡法原理简单,但其缺点是交换次数多,效率低。&
下面介绍一种源自冒泡法但更有效率的方法“选择法”。
3)“选择法”&
&& &选择法循环过程与冒泡法1一致,它还定义了记号k=i,然后依次把a[k]同后面元素比较,若a[k]&a[j],则使k=j.
最后看看k=i是否还成立,不成立则交换a[k],a[i],这样就比冒泡法省下许多无用的交换,提高了效率。&
void choise(int *a,int n)
int i,j,min,
for(i=0;i&n-1;i++)
/*给记号赋值*/
for(j=i+1;j&n;j++)
if(a[min]&a[j])
/*是k总是指向最小元素*/
if(i!=min)
/*当k!=i是才交换,否则a[i]即为最小*/
temp=a[i];
a[i]=a[min];
4)“插入法”&
插入法是一种比较直观的排序方法。
它首先把数组头两个元素排好序,再依次把后面的元素插入适当的位置。
把数组元素插完也就完成了排序。&
void insert(int *a,int n)
for(i=1;i&n;i++)
temp=a[i];
/*temp为要插入的元素*/
while( j&=0&&temp&a[j] )
/*从a[i-1]开始找比a[i]小的数,同时把数组元素向后移*/
a[j+1]=a[j];
但说到高效率,非“快速法”莫属,现在就让我们来了解它。&
5)“快速法”&
&& &快速法定义了三个参数,(数组首地址*a,要排序数组起始元素下标i,要排序数组结束元素下标j).
&& &它首先选一个数组元素(一般为a[ (i&#43;j)/2 ],即中间元素)作为参照,把比它小的元素放到它的左边,比它大的放在右边。
&& &然后运用递归,在将它左,右两个子数组排序,最后完成整个数组的排序。
下面分析其代码:&
void quick(int *a,int i,int j)
k=a[(i+j)/2]; /*选取的参照*/
a[m]&k && m&j
/* 从左到右找比k大的元素*/
while( a[n] &k && n&i )
/* 从右到左找比k小的元素*/
/*若找到且满足条件,则交换*/
temp=a[m];
a[m]=a[n];
while(m&=n);
quick(a,m,j);
/*运用递归*/
quick(a,i,n);
6)“shell法”&
shell法是一个叫 shell 的美国人与1969年发明的。
它首先把相距k(k&=1)的那几个元素排好序,再缩小k&#20540;(一般取其一半),再排序,直到k=1时完成排序。
下面让我们来分析其代码:&
void shell(int *a,int n)
int i,j,k,x;
/*间距值*/
while(k&=1)
for(i=k;i&n;i++)
while(j&=0&&x&a[j])
a[j+k]=a[j];
/*缩小间距值*/
上面我们已经对几种排序法作了介绍,现在让我们写个主函数检验一下。&
#include&stdio.h&&
/*别偷懒,下面的&...&代表函数体,自己加上去哦!*/&
void bubble(int *a,int n)&
void choise(int *a,int n)&
void quick(int *a,int i,int j)&
void insert(int *a,int n)&
void shell(int *a,int n)&
/*为了打印方便,我们写一个print吧。*/&
void print(int *a,int n)&
for(i=0;i&n;i&#43;&#43;)&
printf(&%5d&,a[i]);&
printf(&\n&);&
{& /*为了公平,我们给每个函数定义一个相同数组*/&
int a1[]={13,0,5,8,1,7,21,50,9,2};&
int a2[]={13,0,5,8,1,7,21,50,9,2};&
int a3[]={13,0,5,8,1,7,21,50,9,2};&
int a4[]={13,0,5,8,1,7,21,50,9,2};&
int a5[]={13,0,5,8,1,7,21,50,9,2};&
printf(&the original list:&);&
print(a1,10);&
printf(&according to bubble:&);&
bubble(a1,10);&
print(a1,10);&
printf(&according to choise:&);&
choise(a2,10);&
print(a2,10);&
printf(&according to quick:&);&
quick(a3,0,9);&
print(a3,10);&
printf(&according to insert:&);&
insert(a4,10);&
print(a4,10);&
printf(&according to shell:&);&
shell(a5,10);&
print(a5,10);&
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:747297次
积分:5760
积分:5760
排名:第3165名
原创:99篇
转载:21篇
评论:191条
(3)(7)(5)(5)(7)(1)(3)(1)(1)(2)(1)(12)(9)(48)(1)(4)(10)博客访问: 3776989
博文数量: 1287
博客积分: 12444
博客等级: 上将
技术积分: 12483
注册时间:
出淤泥而不染,濯清涟而不妖,中通外直,不蔓不枝,香远益清,亭亭净植,可远观而不可亵玩焉。
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: C/C++
&&&&&&& 看下面两条语句:
int i = 3;int k=(++i)+(++i)+(++i);
&&&&&&& 执行后k的值是多少?不同的编译器有不同的结果,可能是16(gcc),也可能是18(vc)。为什么会这样呢?&&&&&&& 在C++标准中有一个很重要的概念:sequence points(顺序点)。原文定义如下:
At certain specified points in the execution sequence called sequence points,
all side effects of previous evaluations shall be complete and no side
effects of subsequent evaluations shall have taken place.
&&&&&&& 大致意思是说:在执行顺序中某些指定的点叫做顺序点。这个点上,之前所有的赋值所产生的副作用都已发生完成,并且在其后的赋值不能在该点产生任何副作用。&&&&&&& 那么什么是副作用?C++标准指出:访问一个由可变的左值(volatile lvalue)指派的对象(basic.lval),修改一个对象,调用库I/O函数,或者调用函数等所有这些能够改变执行环境的状态的操作都是副作用。&&&&&&& 听起来感觉有些绕口而且不容易理解。那么来看看都有哪些点是顺序点也许会帮助我们更好的理解。
分号;未重载的逗号运算符的左操作数赋值之后(即“,”处);未重载的”||”运算符的左操作数赋值之后(即“||”处);未重载的“&&”运算符的左操作数赋值之后(即"&&"处);三元运算符“? : ”的左操作数赋值之后(即“?”处);在函数所有参数赋值之后但在函数第一条语句执行之前;在函数返回值已拷贝给调用者之后但在该函数之外的代码执行之前;每个基类和成员初始化之后。
&&&&&&& 那么这个顺序点有什么用呢?在标准中规定:在两个顺序点之间,一个数值对象最多只能由表达式赋值修改一次。而对于其他的行为不给予明确定义。&&&&&&& 那么在回过头来看这条语句:
int k=(++i)+(++i)+(++i);
&&&&&&& 由于此处顺序点就是分号,因此这条语句应该只修改一次i的值。对于多次的i自增值运算,由于标准并没有明确定义,因此各个编译器处理这种情况的方法也不同。这就出现了本文开头的结果。
阅读(2046) | 评论(0) | 转发(0) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。

我要回帖

更多关于 c语言顺序结构程序 的文章

 

随机推荐