非常头疼的C语言编程问题!!!!!!!

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了非常头疼的C语言编程问题!!!!!!!相关的知识,希望对你有一定的参考价值。

用TC编程:先创建指针,然后创建文件,随机从1000个数中选取2个数做加法和减法,并且输出它.
随机产生1000个数!!!! 循环50次并且每次从中选2了相加

一、指针的基本概念
如同其它基本类型的变量一样,指针也是一种变量,但它是一种把内存地址作为其值的变量。因为指针通常包含的是一个拥有具体值的变量的地址,所以它可以间接地引用一个值。

二、指针变量的声明、初始化和运算符
声明语句

- class="code" width="100%">int *ptra, a; ->

声明了一个整型变量a与一个指向整数值的指针ptra,也就是说,在声明语句中使用*(称为“间接引用运算符”)即表示被声明的变量是一个指针。指针可被声明为指向任何数据类型。需要强调的是,在此语句中变量a只被声明为一个整型变量,这是因为间接引用运算符*并不针对一条声明语句中的所有变量,所以每一个指针都必须在其名字前面用前缀*声明。指针应该用声明语句或赋值语句初始化,可以把指针初始化为0、NULL或某个地址,具有值0或NULL的指针不指向任何值,而要想把某个变量地址赋给指针,需使用单目运算符&(称为“地址运算符”)。

例如程序已用声明语句

- class="code" width="100%">int *ptra, a=3; ->

声明了整型变量a(值为3)与指向整数值的指针a,那么通过赋值语句

- width="100%">ptra=&a; ->

就可以把变量a的地址赋给指针变量ptra。需要注意的是不可将运算符&用于常量、表达式或存储类别被声明为register的变量上。被赋值后的指针可以通过运算符*获得它所指向的对象的值,这叫做“指针的复引用”,例如打印语句

- width="100%">printf("%d", *ptra); ->

就会打印出指针变量ptra所指向的对象的值(也就是a的值)3。如果被复引用的指针没有被正确的初始化或没有指向具体的内存单元都会导致致命的执行错误或意外地修改重要的数据。Printf的转换说明符%p以十六进制整数形式输出内存地址,例如在以上的赋值后,打印语句

- width="100%">printf("%p", &a);
printf("%p", ptra); ->

都会打印出变量a的地址。

三、指针表达式和算术运算以及数组、字符串和指针的关系
在算术表达式、赋值表达式和比较表达式中,指针是合法的操作数,但是并非所有的运算符在与指针变量一起使用时都是合法的,可以对指针进行的有限的算术运算包括自增运算(++)、自减运算(--)、加上一个整数(+、+=)、减去一个整数(-或-=)以及减去另一个指针。

数组的各元素在内存中是连续存放的,这是指针运算的基础。现在假设在一台整数占4个字节的机器上,指针ptr被初始化指向整型数组a(共有三个元素)的元素a[0],而a[0]的地址是40000,那么各个变量的地址就会如下表所示:

- width="89">表达式 -> - width="54" bgcolor="#ffffff">ptra -> - width="64" bgcolor="#ffffff">&a[0] -> - width="55" bgcolor="#ffffff">&a[1] -> - width="93" bgcolor="#ffffff">&a[2] ->
- width="89">表达式的意义 -> - width="54" bgcolor="#ffffff">指针ptra的值 -> - width="64" bgcolor="#ffffff">元素a[0]的地址 -> - width="55" bgcolor="#ffffff">元素a[1]的地址 -> - width="93" bgcolor="#ffffff">元素a[2]的地址 ->
- width="89">表达式的值 -> - width="54" bgcolor="#ffffff">40000 -> - width="64" bgcolor="#ffffff">40000 -> - width="55" bgcolor="#ffffff">40004 -> - width="93" bgcolor="#ffffff">40008 ->

必须注意,指针运算不同于常规的算术运算,一般地,40000+2的结果是40002,但当一个指针加上或减去一个整数时,指针并非简单地加上或减去该整数值,而是加上该整数与指针引用对象大小的乘积,而对象的大小则和机器与对象的数据类型有关。例如在上述情况下,语句

- width="100%">ptra+=2; ->

的结果是40000+4*2=40008, ptra也随之指向元素a[2],同理,诸如语句

- width="100%">ptra-=2;
ptra++;
++ptra;
ptra--;
ptra--; ->

等的运算原理也都与此相同,至于指针与指针相减,则会得到在两个地址之间所包含的数组元素的个数,例如ptra1包含存储单元40008,ptra2包含存储单元40000,那么语句

- width="100%">x = ptra1 - ptra2; ->

得到的结果就是2(仍假设整数在内存中占4个字节)。因为除了数组元素外,我们不能认为两个相同类型的变量是在内存中连续存储的,所以指针算数运算除了用于数组外没有什么意义。

如果两个指针类型相同,那么可以把一个指针赋给另一个指针,否则必须用强制类型转换运算符把赋值运算符右边的指针的类型转换为赋值运算符左边指针的类型。例如ptr1是指向整数的指针,而ptr2是指向浮点数的指针,那么要把ptr2的值赋给ptr1, 则须用语句

- width="100%">ptr1 = (int *) ptr2; ->

来实现。唯一例外的是指向void类型的指针(即void *),因为它可以表示任何类型的指针。任何类型的指针都可以赋给指向void类型的指针,指向void类型的指针也可以赋给任何类型的指针,这两种情况都不需要使用强制类型转换。

但是由于编译器不能根据类型确定void *类型的指针到底要引用多少个字节数,所以复引用void *指针是一种语法错误。

可以用相等测试运算符和关系运算符比较两个指针,但是除非他们指向同一个数组中的元素,否则这种比较一般没有意义。相等测试运算符则一般用来判断某个指针是否是NULL,这在本文后面提到内存操作中有一定的用途。

C语言中的数组和指针有着密切的关系,他们几乎可以互换,实际上数组名可以被认为是一个常量指针,假设a是有五个元素的整数数组,又已用赋值语句

- width="100%">ptra = a; ->

将第一个元素的地址赋给了指向整数的指针ptra,那么如下的一组表达式是等价的:

a[3] ptra[3] *(ptra+3) *(a+3)

它们都表示数组中第四个元素的值。

又因为C语言中的字符串是用空字符('\0')结束的字符数组,所以事实上,字符串就是指向其第一个字符的指针。但是还是要提醒大家,数组名和字符串名都是常量指针,他们的值是不可被改变的,例如程序段

- width="100%">char s[]="this is a test.";
for (;*s='\0';s++)
printf("%c", *s); ->

是错误的,因为它试图在循环中改变s的值,而s实际上是一个常量指针。

在本部分的最后,说一说指向函数的指针。指向函数的指针包含了该函数在内存中的地址,函数名实际上就是完成函数任务的代码在内存中的起始地址。函数指针常用在菜单驱动系统中。

四、指针的应用、内存操作和简单的数据结构
其实初学者在学习指针时,其困难之处往往不在于理解其中的基本概念,而在于不知道指针究竟有何用处,什么时候应该用指针去解决实际问题。以下我就将指针的主要功能作一个简单的介绍。

A——调用函数时能修改两个或两个以上的值并将其返回调用函数
我们在没有学习指针之前便涉及了函数的使用,在函数中使用return语句可以把一个值从被调用的函数返回给调用函数(或从被调用函数返回控制权而不返回一个值),但是在实际应用中常常需要能够修改调用函数中的多个值,这就必须用到指针。

给函数传递参数的方式有两种,即传值和传引用,C语言中的所有函数调用都是传值调用,但可以用指针和间接引用运算符模拟传引用调用。在调用某个函数时,如果需要在被调用的函数中修改参数值,应该给函数传递参数的地址。当把变量的地址传递给函数时,可以在函数中用间接引用运算符*修改调用函数中内存单元中的该变量的值。比如我们无法在只用传值调用的情况下在函数中完成两个数的交换,因为它要求修改两个参数的值,但通过传地址模拟传引用调用即可轻松地实现这一点,例如函数:

- width="100%">void swap( int *a, int *b)

int temp;
temp=*a;
*a=*b;
*b=temp;
->

就实现了两个整数的交换,在调用函数swap时,需要用两个地址(或两个指向整数的指针)作为参数,比如

- width="100%">swap(&num1, &num2); ->

其中num1和num2是两个整型变量,这样他们的值通过调用swap函数便得到了交换。需要指出的是,传递数组不需要使用运算符&, 因为数组名实际上是一个常量指针,然而传递数组元素就不同了,仍需要以元素的地址作为参数才能在函数中修改元素的值并返回调用函数。

B——能更加方便地处理字符串与数组
如前文所述,数组名与字符串名都是常量指针,指针和数组名有时候是可以替代的。用指针编写数组下标标达式可节省编译时间,而且有时表示相对偏移量会更加方便,这方面的技巧本文就不多介绍了。

C——能动态地分配空间并直接处理内存地址
不像BASIC等一些其他高级语言,C语言要求同一层次中声明语句不能被置于任何执行语句之后,这决定了C中的数组是完全静态的,因为数组的长度必须在声明的时候确定,也就是说不能通过输入语句之类的方法临时决定数组的大小,要想建立和维护动态的数据结构就需要实现动态的内存分配并运用指针进行操作。

现在将关于内存操作的几个重要函数列表如下:

- width="22%">
函数

-> - width="78%">
作用

->
- width="22%" bgcolor="#ffffff">void *calloc(size_t nmemb, size_t size); -> - width="78%" bgcolor="#ffffff">为含nmemb个对象的数组分配空间,每个对象的大小为size.所分配的空间初始化成全零。函数calloc返回空指针或一个指向所分配空间的指针。 ->
- width="22%" bgcolor="#ffffff">void free(void *ptr); -> - width="78%" bgcolor="#ffffff">回收ptr所指向的空间,即使该空间可用于再分配。 ->
- width="22%" bgcolor="#ffffff">void malloc(size_t size); -> - width="78%" bgcolor="#ffffff">分配由size指定大小的对象空间。函数返回一个空指针或指向所分配空间的指针。 ->
- width="22%" bgcolor="#ffffff">void *realloc(void *ptr, size_t size); -> - width="78%" bgcolor="#ffffff">将ptr所指对象的大小改为size指定的大小。在新旧空间的较小空间中的对象的内容不被改变。如果新空间大,该对象新分配部分的值是不确定的。若ptr是空指针,函数realloc的行为类似指定空间的函数malloc。如果size为零且ptr不是空指针,其释放所指向的对象。函数realloc返回一个空指针或指向可能移动了的分配空间的指针。 ->
- width="100%" bgcolor="#ffffff" colspan="2">注:这些函数的原型在通用工具头文件 中。 ->

运用这些函数我们可以实现动态数据结构的处理,举个简单的例子,我们要模拟建立变长数组,只需使用如下语句:

- width="100%">int n, *ptr;
scanf("%d", &n);
ptr=malloc(n*sizeof(int)); ->

这样,系统就分配了存放有n个元素的数组所需要的空间,ptr存储了分配内存的首地址,之后通过指针的复引用即可进行赋值等操作。需要提醒的是,如果没有可用的内存,在调用malloc函数时就返回NULL指针,所以这时一般应判断一下指针是否为NULL再进行下一步操作。

由此可见,尽管指针通常以变量的地址作为其值,但有时候它指向的地址根本就没有变量名,这时候只能通过复引用指针进行一系列的操作。

D——能有效地表示复杂的数据结构(本部分内容建议初学者在弄清一切基本概念之前可以先不看)
在初学者接触令人费解的数据结构之前,必须先了解两个新的概念——“指向指针的指针”(也称二级指针)和“自引用结构”。

一级指针包含一个地址,该地址中存放具体的值,而二级指针包含的地址中存放的是另一个地址,因此也可以说二级指针是指向指针的指针。

声明一个二级指针要使用**,例如:

- width="100%">int **ptr; ->

声明了一个指向整数的二级指针。二级指针到底有什么用呢?其实就像通过给函数传递地址,我们就可以修改具体的值一样,给一个函数传递二级指针,就可以将修改地址并将其返回调用函数,这在数据结构的管理上相当重要。

一般的结构包含若干成员,而自引用结构必定包含一个指针成员,该指针指向与自身同一个类型的结构。例如,如下结构定义就定义了一个自引用结构:

- width="100%">struct node
int data;
struct node *nextptr;
; ->

通过链节nextptr可以把一个struct node类型的结构与另一个同类型的结构链在一起。我们可以通过自引用结构建立有用的数据结构,如链表、队列、堆栈和树。考虑到篇幅问题与难度关系,笔者仅就比较基本的单向链表给出机械工业出版社《C程序设计教程》(第二版)中的源程序。我不推荐初学者过早接触数据结构,因为其中涉及了二级指针和自引用结构的大量应用,在打牢基础之前基本不可能完全读懂程序就更不要说运用了。对数据结构有兴趣的同学可以再和我进行交流。

五、学习指针与内存的一点建议
很多人在刚接触指针的时候可能会觉得比较抽象,所以在练习的时候总是回避指针的应用,尽量用原有的方法解决问题,越是这样就越难进步。我建议大家即使面对不用指针也能解决的问题,也应该尝试使用指针,尤其是模拟函数的传引用调用等,只要多多练习,其实掌握基本概念还是很轻松的。再接下来就是要多思考什么时候用指针显得更方便和实用,逐渐掌握怎么用指针的真谛。
参考技术A #include <stdio.h>
#include <stdlib.h>

void main()

FILE *fp;
char filename[10],ch;
int num,a1,a2;
printf("Please input the name of the file you want to create:");
scanf("%s",filename);
if ( (fp = fopen(filename,"wb+")) == NULL;

printf("Error: can not open file.\n");
exit(0);


//随机生成1000个数,然后保存到文件中
for ( int i = 0; i < 1000; ++i)
//以下两个函数是在网上查的,没验证

//在第一次调用随机数之前,要调用一次这个函数,
//这个函数称为“随机种子函数”
randomize();

//随机函数:random(int n)的用法:
//随机返回一个 0~ (n-1) 之间的整数,
//如: int x = random(100),则x值将是0到99之间的一个数。
num = -32768 + random(65536);//(-32768--32767 int型整数的范围)
fprintf(fp, "%d",num);

for (int i = 0; i < 50; ++i)

// rewind(fp);
//从文件中随机读取两个数
randomize();
a1 = random(1000);
a2 = random(1000);
fseek(fp,a1,SEEK_SET);
fseek(fp,a2,SEEK_SET);
printf(" %d + %d = %d \n", a1, a2 , a1+a2);//因为两个数是随机的整数,所以它们的和可能会溢出!不过我没处理

fclose(fp);


题中说的先创建指针,我不知道是什么意思,我看不必用指针,那样反而不直观。本回答被提问者采纳
参考技术B 很困惑,这样简单的题目也叫做复杂?
想要编一个给你都觉得浪费了时间呀。
你说说你具体什么地方有困难吧,
发邮件给我,我可以教你。
参考技术C 这1000个数是文件中已经有的吗?
是不是结果输出到文件?
参考技术D 无聊

搬家] 插件化软件设计的头疼问题以及可能的解决思路

11年的文章,当时在做系统集成,实际上当时的思路到现在我还在琢磨,只不过后来就不做系统集成了,也一直没机会深入下去解决这个问题。

 

==== 正文 ====

 

一直以来做的项目中有很大一部分工作量都是有关集成设备的工作。为了方便扩展以支持更多厂家的设备,但在这个过程中遇到了非常头疼的问题,在这里我把问题描述一下,欢迎大家来探讨。

问题描述:
所谓设备集成,多数情况下可以简化为SDK的集成(这个比较方便,有时候我们宁愿直接集成协议,但是厂家有厂家的考虑)。针对这种情况,我们的系统采用了比较典型的插件式设计思路,把功能大体一致的设备封装为统一接口的动态共享库,运行时根据数据库配置动态加载调用,同时宿主软件本身也提供了一套接口以便下层动态库来回调获得必要的信息。
我把问题描述到这个程度,估计很多人已经在窃笑了——是的,就是动态库版本问题……

应该说在多数情况下这种插件机制可以很好的应付设备集成需求,并且可以很容易的跨平台实现(这很重要),但是有一种情况,就是在一个进程中使用同一个共享库,却妄图加载两个版本。这时候情况就变得非常混乱。看图:(工具简陋,箭头我没画,大概意思应该都明白)

技术分享

图中的问题很明了:现在的嵌入式设备厂家众多,但是跟手机行业一样,有很多厂家生产的是“山寨机”,也就是基于嵌入式解决方案做二次开发做出来的产品(比如国内的周立功和华为海思的方案)。针对一些复杂的功能比如解码器,解决方案提供商会以动态库的形式提供上位机开发包,也就是图中的厂家底层库那个模块,然后设备厂家封装SDK要调用这些动态库。如果要在一个软件内集成两个厂家的设备A和B,俩厂家用的是一个品牌的硬件解决方案,但是版本不一样,问题就出现了。这时候底层库的文件名是一样的,但是版本不一样,一个进程做不到同时加载两个版本的底层库。如果解决方案提供商做不到完全的版本兼容,一定会有一种设备用不了。

针对这个问题我曾经尝试用两个方法解决过:

方法一:
很猥琐的方法。当时客户的平台为win32,设备都是遗留设备,厂家的人根本找不到,为了快速解决问题我直接用UltraEdit打开一种设备的SDK文件把里面调用文件名字符串改了一个名字,然后把底层库也改了一个名字,问题就解决了……后来我一直不太放心,怕出问题,还好一直没出问题。后来问同事他们说也这么干过……囧。

方法二:
在设计新版软件的时候,一方面行业的规范性增强了,地区性行业标准相继出现,另一方面新系统主打整体解决方案,不针对老项目改造,这个问题不被重视。但是我预感到基于商业的考量肯定还会出现这种问题。我实验了一种方法,就是把设备这一块封装到另一个进程中,和主程序做数据交互,或者另一个进程也实现一部分界面,直接嵌入到主程序界面中(就像是Smplayer)。我在windows上做了一个原型程序,功能没有问题,但是最终新版软件没有考虑这个方案。原因主要是一方面这个机制和主程序的机制差别很大,封装到一起很不容易,另一方面要考虑跨平台,在linux平台上这样做性能不好,也没有办法优化,甚至某些功能实现不了(经过研究这些确实是有结论的)。

目前这个问题我还没有太好的解决方案,只能就事论事针对项目的特点逐个解决。
 
应该说造成这个问题的原因和技术是没有关系的,主要还是因为规范不到位,各厂家各行其是,盲目追求”大集成“,对可用性的关注不足。这种问题本事不足为道,但是往往是这种“非智力问题”最困扰人。在这里说这个问题可能不合时宜,就算是带来一点不一样的空气吧。

 

以上是关于非常头疼的C语言编程问题!!!!!!!的主要内容,如果未能解决你的问题,请参考以下文章

别头疼了,你要的算法和数据结构的学习路线来了!

C语言switch问题~~~

能让你更早下班的Python垃圾回收机制

第一周作业.

初次接受C语言游戏程序感受

初次接受C语言游戏程序感受