斯坦福大学开放课程--编程范式(四)

综述

本节课的主要内容是关于泛型数据的拷贝,虽然是使用C语言实现,并且没有用到C++中的模板这种泛型编程技术,但是效果却非常好。本节内容紧接上节所将的字节位拷贝的知识,充分利用了字节拷贝技术。

笔记

由于内容和例子不断深入,实际核心内容则比较集中,因此这里只进行总结讨论。

引例

本节所有的例子都是针对于数据交换来进行的,从最简单的例子开始,不断深入。 开始是关于一个最简单的整数数据的交换实例:

void swap(int a, int b){int tmp = a;a = b;b = tmp;
}

此例子非常简单,只需要构造一个简单的中间临时变量tmp用来存放a的值,并且交换赋值相关的数据,就可以达到交换的目的。

但是,此实例有一个缺陷,就是值传递,而不是引用传递,这样,传递的值虽然改变,但是只想原始变量的单元却没有改变,具体来说就是:

a = 23;
b = 34
swap(a, b);

执行上面一段语句会发现,其实a,b的值并没有交换,原因和C/C++的参数的值传递以及指针传递有关系。函数调用的时候,只会拷贝a,b的值,因此调用swap的时候,交换的是形参,实际参数的值并没有改变。

要实现真正参数传递的效果,需要用指针的形式来实现:

void swap(int *vp1, int *vp2) {int a = *vp1;*vp1 = *vp2;*vp2 = *vp1;
}

再次调用swap(&a, &b)的时候,就会修改掉原来的值,因为这里传递过去的就是指针,所以,对vp1,vp2的操作 就是对指向单元a,b的操作,所以能够修改对应的值。

泛型交换与拷贝

上面的例子,只是对某种特定的类型进行交换,比如int类型,如果想对double类型等进行交换,只需要修改其类型为double即可,其他类型类似。 但是考虑到需要对多种不同类型进行交换,是否存在一种通用的方法呢? 在C++中,可以用模板template技术,然而这里,回想起上节课中讲到的字节操作,能否利用字节的拷贝来实现呢?答案是肯定的。

void swap(void *vp1, void *vp2, int size){char buffer[size];memcpy(buffer, vp1, size);memcpy(vp1, vp2, size);memcpy(vp2, buffer, size);
}

调用的时候,字号需要给定某个类型,即可实现。比如,通过:

double a = 23.0, b = 34.0;
swap(&a, &b, sizeof(double));

当然,对于结构体也可以通过这种形式来进行拷贝。

关于上面例子的几点说明:

  1. 这里声明数组的方式,使用的大小size是可变的,这只在某些编译器中支持,这里只是为了说明字符拷贝的方式,例子的重点在于交换。当然可以使用malloc或者new来动态分配大小可变的空间。
  2. 这里使用memcpy(dest, src, size)这个函数来进行内存单元的拷贝,注意此函数并不关心你的数据类型,单纯的进行单元的拷贝而已,所以虽然编译可能通过,但是还需要自己进行判断和控制。
  3. 这个例子的亮点就在于void*的使用,通过它能够实现泛型,即针对于int,short,char,struct等类型都能够保证能够拷贝交换成功。
  4. template和这里的区别和优缺点。注意到使用模板的话,编译后,会为每种类型都生成一种代码,比如int对应的,float对应的,这样如果调用次数很多的话,代码体积会增大,冗余过多。而这里编译出来就一套代码,更加简洁。
  5. 这里传递一个参数大小size是因为,由于泛型指针void*的存在,所以编译器并不知道要拷贝多少个字节,所以,需要你手动控制并指定一个大小。

存在的问题

由于编译器会很容易的放过void*带来的错误,所以如果两个类型不同的数据调用此函数,就会出现问题:

double a = 23.0;int b = 345;swap(&a, &b, sizeof(double));

这里,double和int类型占据的数据空间的大小是不同的,因此,如果单纯的直接调用这个函数,就会出错,简单的结果就是,截断拷贝或者多拷贝数据。 比如,int类型拷贝到double数据空间的时候,只有前面2个字节拷贝了,后面的原来double数据的两个字节仍然保留了;或者说double拷贝到int的时候,可能会多拷贝两个字节到int后面的数据,造成出错。具体的方式,与后面一个参数sizeof(double)或者sizeof(int)有关系。

初学者容易犯的错误

  1. 使用void * tmp = vp1,而不是前面锁讲到的char buffer[size],这个错误的原因是由于不理解void不是一个类型,不像int,double等属于一个类型,所以错误。void 只能用作函数参数,返回值才可行。但是可以使用 void * tmp = (int )&a这类的用法,因为具体的类型即可以赋值给一般的类型,你只有给定了一个具体的类型,才能让编译器知道规则,才能编译通过。
  2. 指针的拷贝,何时使用引用地址的问题。一个简单的例子出发,

char * husband = strdup("Fred"); char * wife = strdup("Wilma");

如果想交换两人所指向的空间内容,正确的做法是:

swap(&hustband, &wife, sizeof(char *))

也就是说,这里要交换的是指针的地址,交换之后,husband的内容发生了变化,内容变成了原来wife的内容,由于本身是地址,所以内容变了,实际上所只想的地址也变了,现在husband指向原来wife所指向的地址,而wife指向原来husband指向的地址。

一个错误的例子就是,swap(husband, wife, &sizeof(char *)),这样,交换的实际上是他们锁指向的内容,即存放Fred和Wilama的单元中的内容会交换,而且,由于char *是四个字节,因此交换的就只有四个字节的内容。 为何会如此呢?因为上面的例子,比如要交换a,b单元的内容,传入的就是a,b的地址&a, &b,同样,这里我直接传入指针,当然交换的是他们指向的单元的内容,即两个字符串。 所以要交换两个指针的内容,就要交换他们的地址,即指针的地址,指针的指针。

另外一个例子

思考一个下面线性搜索的例子,

int * lsearch(int key ,int* array, int size){for (int i = 0; i < size; i++)  {if(array[i] == key)return i;}
}

上面的这段代码,直接返回的就是找到索引的那个下标。

利用位比较的方式来实现

同样,为了应用上面我们学到的知识,这里想要泛型比较,搜索,如何实现? 例如,对于这里的int,能否用一个struct,一个double或者其他类型。 答案仍然是肯定的,只不过,我们需要对其中编译器的工作,比较的大小进行控制而已。

int *lsearch(void *key, void *base, int size, int elementSize){for (int i = 0; i < size; i++) {void * elemeAddr = (char *)base + i * elementSize; if (memcmp(key, elemeAddr, elementSize) == 0)return elemeAddr;}
}

这里的几个说明点就是,首先,传入参数的size就是要比较的数组的大小,类型我们不知道,就用void *类型,然后要传入每一个类型的大小,elementSize,这个标记了每一个数组成员的大小,正因为有这个我们才可以精准的定位到具体的单元,利用for循环来比较每一个单元和key的关系。而这里比较用的memcmp来进行,比较的字节数就是elementSize,传入两个指针即可,而比较的指针就是数组的每一个单元的地址,即elemeAddr而已。

编程范式(斯坦福大学)学习笔记《四》相关推荐

  1. 编程范式(斯坦福大学)学习笔记《二》

    斯坦福大学开放课程:编程范式学习笔记<二> 本课讲述了C/C++关于int,float等数据的底层表示,以及赋值操作所进行的处理.本节内容比较简单,应该属于组成原理的基础知识,各种码的表示 ...

  2. 编程范式(斯坦福大学)学习笔记《十二》

    本节课讲解的主要是预处理器,编译和连接的异同. #define有两个功能:一个是定义常量,一个是作为宏. //定义常量 #define w 40 #define h 80 #define pere 2 ...

  3. 编程范式(斯坦福大学)学习笔记《十一》

    上次的课中都是使用C代码生成汇编代码,这次课中将使用C++代码生成汇编代码,对比结果发现这两种语言最终生成的代码形式很像. C语言版本: void foo() {int x;int y;x=11;y= ...

  4. Netty学习笔记二网络编程

    Netty学习笔记二 二. 网络编程 1. 阻塞模式 阻塞主要表现为: 连接时阻塞 读取数据时阻塞 缺点: 阻塞单线程在没有连接时会阻塞等待连接的到达,连接到了以后,要进行读取数据,如果没有数据,还要 ...

  5. NVIDIA可编程推理加速器TensorRT学习笔记(二)——实操

    NVIDIA可编程推理加速器TensorRT学习笔记(二)--实操 ​ TensorRT 是 NVIDIA 自家的高性能推理库,其 Getting Started 列出了各资料入口,如下: 本文基于博 ...

  6. css中怎么加入立体模型,CSS学习笔记二:css 画立体图形

    继上一次学了如何去运用css画平面图形,这一次学如何去画正方体,从2D向着3D学习,虽然有点满,但总是一个过程,一点一点积累,然后记录起来. Transfrom3D 在这一次中运用到了一下几种属性: ...

  7. wxpython应用程序对象与顶级窗口_wxPython学习笔记(二)

    如何创建和使用一个应用程序对象? 任何wxPython应用程序都需要一个应用程序对象.这个应用程序对象必须是类wx.App或其定制的子类的一个实例.应用程序对象的主要目的是管理幕后的主事件循环. 父类 ...

  8. NumPy学习笔记 二

    NumPy学习笔记 二 <NumPy学习笔记>系列将记录学习NumPy过程中的动手笔记,前期的参考书是<Python数据分析基础教程 NumPy学习指南>第二版.<数学分 ...

  9. qml学习笔记(二):可视化元素基类Item详解(上半场anchors等等)

    原博主博客地址:http://blog.csdn.net/qq21497936 本文章博客地址:http://blog.csdn.net/qq21497936/article/details/7851 ...

最新文章

  1. VS 打开No EditorOptionDefinition export found for the given option name问题解决
  2. @Profile-根据不同环境注入bean
  3. 【Unity】2.11 了解游戏有哪些分类对你开阔思路有好处
  4. django models模型 内部类 class Meta 简介
  5. 不需要许可认证即可解决WIN2003不能超过2个远程终端用户试用120天的问题
  6. 网络工程师 名词解释
  7. 网站关键词选择的四大步骤
  8. OAuth2.0+SpringSecurity+Jwt实现系统的登录认证,用户授权
  9. *sql注入实战--记一次绕过WTS-WAF拦截注入**
  10. ubuntu 启动、退出 startx界面
  11. 使用Arcmap将WGS84坐标转换成国家大地坐标系2000
  12. python列表sort倒序输出_Python 列表sort()添加key和reverse参数操作方法|python基础教程|python入门|python教程...
  13. 二分图的判定最大匹配
  14. 什么是幻读?以及如何解决幻读?
  15. 《强悍》小妹妹买裤子砍价的全过程!
  16. 纽约2050交通发展战略——高效移动性
  17. AE C# 图片标注背景透明
  18. 有一个数列,其前三项分别为1、2、3,从第四项开始,每项均为其相邻的前三项之和的1/2,问:该数列从第几项开始,其数值超过1200。
  19. 微信公众平台开发总结
  20. ERROR=(CODE=1153)

热门文章

  1. GD32W515系列Wi-Fi MCU资料
  2. 阿里云天池大赛赛题(机器学习)——天猫用户重复购买预测(完整代码)
  3. ISP——Gamma Correction
  4. WIN7 装2010 没反应,不要虚拟光驱,解压出来就可以安装了
  5. 企业固定资产盘点系统哪个更好用
  6. Mysql、Oracle、DM、Tbase数据库差异性
  7. curl 返回CURLE_COULDNT_RESOLVE_HOST、CURLE_COULDNT_CONNECT
  8. 给陈景润之子陈由伟的一封公开信
  9. 如何跑通平头哥RISC-V E902的仿真验证
  10. java中原子变量AtomicInteger等用法