指针
- 格式:doc
- 大小:273.50 KB
- 文档页数:91
指针的名词解释指针是计算机编程中常用的一种数据类型,它存储了内存中某个变量的地址。
通过指针,我们可以间接地访问和修改内存中的数据,进而实现复杂的计算和数据处理。
1. 指针的定义和声明指针变量是一种特殊的变量,其存储的值是另外一个变量的地址。
我们可以通过将变量名前面加上一个"*"符号来声明指针变量。
例如,int* p; 表示声明了一个名为p的整型指针变量。
2. 指针与内存地址的关系每个变量都存储在计算机的内存中,并被赋予一个唯一的地址。
指针变量存储的值就是某个变量的地址,通过指针,我们可以直接操作和访问内存中的数据。
这种直接访问内存地址的方式,赋予了指针在编程中非常重要的地位。
3. 指针的应用指针在编程中起到了非常重要的作用,它们广泛应用于各种算法和数据结构中。
以下是指针的几个常见应用:a. 动态内存分配:通过指针可以在程序运行时动态地分配和释放内存。
这种灵活性可以大大提高程序的效率和资源利用率。
b. 数据结构中的指针:指针在链表、树等数据结构中扮演着重要的角色。
通过指针的相互连接,我们可以实现复杂的数据结构和算法。
c. 函数与指针:指针可以作为函数的参数来实现数据传递和共享。
通过传入指针,函数可以直接修改调用者传递的变量,实现更加灵活的数据处理。
d. 指针与数组:数组名本身就是指向数组首元素的指针。
通过指针,我们可以方便地对数组进行遍历和操作,提高了数组的处理效率和灵活性。
4. 指针的注意事项指针在编程中具有强大的功能,但也有一些需要注意的地方:a. 空指针:指针的值可以是空,即指向内存地址为0的情况。
使用指针前,最好先判断其是否为空,以免引起程序的崩溃或意外行为。
b. 野指针:指针变量必须在初始化后才能使用,否则可能会指向无效的内存地址。
使用指针前,务必确保其已正确初始化,避免野指针的问题。
c. 内存泄漏:动态分配的内存需要手动释放,否则会造成内存泄漏。
在不需要使用某块内存时,及时释放它,以避免浪费和程序性能下降。
第9章指针指针是C语言的精华,指针让C语言威力无穷,魅力四射。
为什么scanf函数的实参有时要加一个&操作符,有时却不加?为什么数组作为参数时就可以改变实参的值?这些前面遗留的问题都与指针有关,这些问题的答案均可在本章中找到。
指针是C语言中特殊的数据类型。
整型变量所标识的存储单元中存放整数,浮点型变量中存放浮点数,那么指针变量所标识的存储单元中存放的显然是指针,但是指针是什么呢?9.1 指针类型9.1.1 变量的左值和右值变量用于标识存储单元。
计算机中的内存以字节为单位编号。
编号多为32位的二进制数,从0号开始,即0x0000 0000、0x0000 0001、……、0xffff ffff。
计算机中只用内存编号(又称内存地址)标识内存单元。
如果定义并初始化了一个整型变量如int i = 5;,则计算机中的内存状态可能如图9-1所示。
图9-1 变量i的内存状态图从图9-1可知,整型变量i所标识的存储单元共4个字节,通常以存储单元的首字节地址作为该存储单元的地址,即整型变量i所标识的存储单元的地址为0x0012 ff00,类型为整型。
当取变量i的值时,计算机会把从0x0012 ff00处开始的4个字节作为一个整体,取出其中的内容,然后按整型解码最终得到变量i的值为5。
存储单元如宿舍,其地址像宿舍号(如408),其存储的内容如住宿者(如王五),相关变量名如宿舍的雅称(如liaozhai)。
由以上分析可知,变量既标识存储单元的地址又标识其存储的内容,因此变量比如整型变量i也有两个值。
整型变量i的一个值是地址0x0012 ff00,另一个值是内容5。
变量i在使用时表现为何值呢?例9-1分析语句i = 5; j = i;中整型变量i的值。
分析:语句i = 5;的操作结果是把5放入变量i所标识的存储单元中,也就是把5的补码存入地址为0x0012 ff00的存储单元中,变量i的值此时实为地址0x0012 ff00。
前言指针是C的灵魂,正是指针使得C存在了这么多年,而且将长期存在下去。
事实上,我自己不用C语言写程序已经有一年了,工作中接触到的只有java,python和javascript.最近用C完成了一下类似于OO中的封装(即"类")的概念,顺便把指针复习了下,感觉有必要记一下。
本文中的例子有这样两个概念:任务(Task),执行器(Executor)。
任务有名称(taskName),并且可以执行(execute)。
而执行器与具体任务所执行的内容无关,只是回调(callback)任务的执行方法,这样我们的执行器就可以做的比较通用。
而任务接口只需要实现一个execute方法即可,这样我们的任务就可以是多种多样的,可以通过统一的接口set给执行器执行。
这是面向对象中基本的思想,也是比较常用的抽象方式。
下面我们具体看下例子。
可以想象,main函数大概是这个样子:int main(int argc, char** argv) {Task *t1 = TaskConstruction("Task1",run);//此处的run是一个函数指针Executor *exe = ExecutorConstruction();exe->setTask(t1);exe->begin();exe->cancel();Task *t2 = TaskConstruction("Task2",run2);//此处的run2也是一个函数指针,用于构造一个Task.exe->setTask(t2);exe->begin();exe->cancel();return (EXIT_SUCCESS);}运行结果为:task : [Task1] is ready to run[a = 1.200000, b = 2.300000][(a + b) * (a - b) = -3.850000]cancel is invoked heretask : [Task2] is ready to runanother type of execute,just print out some informationcancel is invoked here好了,下面详细看看实现:定义接口首先,定义Task和Executor两个实体的接口:Task接口,注意其中的_this字段,这个指针在后边有很重要的作用,用于hold整个Task的实例。
然后是一个taskName的字符串,和一个函数指针,这个指针在初始化(构造)Task 时传入。
这个execute()函数比较有意思,它不在内部使用,而是让执行器回调执行的。
#ifndef _ITASK_H#define _ITASK_Htypedef struct Task{struct Task *_this;char *taskName;void (*execute)();}Task;void execute();#endif /* _ITASK_H */执行器接口比Task接口复杂一些,其中包含_this指针,包含一个对Task的引用,然后是对外的接口begin(), cancel ()。
对接口的使用者来说,他们只需要调用接口实例上的setTask(),将任务传递给执行器,然后在适当时期调用begin (),等待任务正常结束或者调用cancel()将其取消掉。
#include "ITask.h"#ifndef _IEXECUTOR_H#define _IEXECUTOR_Htypedef struct Executor{struct Executor *_this;Task *task;char *(*setTask)(Task* task);void (*begin)();void (*cancel)();}Executor;char *setTask(Task *task);void begin();void cancel();#endif /* _IEXECUTOR_H */实现接口#include <stdlib.h>#include "ITask.h"Task *task = NULL;void execute();/** The construction of Task object.* name : the name of the task* execute : execute method of the task **/Task *TaskConstruction(char *name, void(*execute)()){task =(Task*)malloc(sizeof(strlen(name))+sizeof(execute));task->taskName = name;task->execute = execute;task->_this = task;return (Task*)task;//返回一个自身的指针,通过内部的_this指针,两者即可实现封装}/** Destruction of task, not used current time. **/void TaskDestruction(){task->taskName = NULL;task->execute = NULL;task->_this = NULL;task = NULL;}/** private method, should register to executor **/void execute(){task->_this->execute();//调用_this上的execute()方法}执行器的实现一样,稍微复杂一点,构造的时候,将函数指针在内部设置好,当外部调用时动态的执行需要执行的函数,这句话可能有些绕口,这么看:在构造Executor的时候,executor->begin = begin;这条语句是将下面void begin()的实现注册到结构体中,但是要执行什么还是不确切的,当setTask以后,回调函数的地址已经明确:(executor->_this->task = task;),此时调用begin()即可正确的调用到注册的Task上。
#include <stdlib.h>#include "IExecutor.h"Executor *executor = NULL;Executor *ExecutorConstruction(){executor =(Executor*)malloc(sizeof(Executor));executor->begin = begin;executor->cancel = cancel;executor->setTask = setTask;executor->_this = executor;return (Executor*)executor;}void ExecutorDestruction(){executor->begin = NULL;executor->cancel = NULL;executor->setTask = NULL;executor = NULL;}char *setTask(Task *task){executor->_this->task = task;}void begin(){printf("task : [%s] is ready torunn",executor->_this->task->taskName);executor->_this->task->execute();}void cancel(){//这个函数没有实现,只是做了一个占位符,以后如果有多线程,可以用来停止主动线程。
printf("cancel is invoked heren");}其实,两个实现的代码都不算复杂,如果对C的指针理解的稍好,基本就没什么问题了。
在C中使用OO为了试验,我们不妨设计两个不同的Task,一个Task是计算两个数的某四则混合运算,另一个仅仅是用来打印一点信息。
然后我们可以看到,他们使用完全相同的接口来执行:#include <stdio.h>void run(){//计算(a+b)*(a-b)float a, b, r;a = 1.2;b = 2.3;r = 0.0;printf("[a = %f, b = %f]n", a, b);printf("[(a + b) * (a - b)= %f]n",((a+b)*(a-b)));}void run2(){//打印一句话,事实上,这些函数可以做任何事,比如I/O,网络,图片处理,音乐播放等等。
printf("another type of execute,");printf("just print out some informationn"); }然后,在Main中奖他们注册给Task,代码如下所示:#include <stdio.h>#include <stdlib.h>#include "ITask.h"#include "IExecutor.h"extern void run();extern void run2();int main(int argc, char** argv) {//代码的风格上,应该可以看出和OO的风格及其类似。
Task *t1 = TaskConstruction("Task1",run);//new Task("Task 1", run);Executor *exe = ExecutorConstruction();// new Executor();exe->setTask(t1);exe->begin();exe->cancel();Task *t2 = TaskConstruction("Task2", run2);exe->setTask(t2);exe->begin();exe->cancel();return (EXIT_SUCCESS);}程序的输出结果上文中已经可以看到了,这里就不贴了。