C++博客-我的编程乐园-随笔分类-大学公开课http://www.cppblog.com/deercoder/category/19231.html<P><FONT style="FONT-SIZE: 20px" color=#ff0000>积累,坚持!</FONT></P> <P><FONT style="FONT-SIZE: 20px" color=#ff0000>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ---------我是一只IT小小鸟</FONT></P>zh-cnSun, 24 Jun 2012 14:30:22 GMTSun, 24 Jun 2012 14:30:22 GMT60斯坦福大学开放课程--编程范式(四)http://www.cppblog.com/deercoder/archive/2012/06/24/180038.htmldeercoderdeercoderSun, 24 Jun 2012 08:57:00 GMThttp://www.cppblog.com/deercoder/archive/2012/06/24/180038.htmlhttp://www.cppblog.com/deercoder/comments/180038.htmlhttp://www.cppblog.com/deercoder/archive/2012/06/24/180038.html#Feedback0http://www.cppblog.com/deercoder/comments/commentRss/180038.htmlhttp://www.cppblog.com/deercoder/services/trackbacks/180038.html

综述

本节课的主要内容是关于泛型数据的拷贝,虽然是使用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而已。

                                                                                                   ---Written by markdown and HTML file is generated by markdown.



deercoder 2012-06-24 16:57 发表评论
]]>
斯坦福大学开放课程:编程范式学习笔记《二》http://www.cppblog.com/deercoder/archive/2012/05/13/174774.htmldeercoderdeercoderSun, 13 May 2012 10:03:00 GMThttp://www.cppblog.com/deercoder/archive/2012/05/13/174774.htmlhttp://www.cppblog.com/deercoder/comments/174774.htmlhttp://www.cppblog.com/deercoder/archive/2012/05/13/174774.html#Feedback0http://www.cppblog.com/deercoder/comments/commentRss/174774.htmlhttp://www.cppblog.com/deercoder/services/trackbacks/174774.html
bool    1byte
char    1 byte
short   2 bytes
int        2-4 bytes
float     4bytes
double  8bytes

binary  digit ==> bit
1byte = 8种bit的组合,即共有2的8次方这么多种选择。
每一位都有权值,对应的,2的0次方依次往上递增。

short:2 bytes表示,共有2的16次方表示
但是并不是完全表示正数,最开始的符号位,1为负数,0为正数(实际上就是反码的表示方法)
缺陷: +7 + (-7),最终得到的数值用反码来表示的话不是0。
因此,计算机处理起来很麻烦,以备淘汰

更优的办法是:补码表示,取反加1.
为何? 从计算机表示的角度出发,+7 加上什么为0呢? 接近0的数是,全部为1的(-1),然后加上1就可以溢出符号位,从而表示为0.
因此顺理成章的,负数的表示就是,正数的基础上,取反, 然后加1.

一个例子:
char ch = 'A';
short s = ch;(不需要类型转换)
cout << s结果是,65.
如何做的呢?计算机,just copy 
bit pattern copy的形式,不管你的类型如何,直接进行拷贝,由于short比char多一个字符,所以拷贝的前一个byte直接用全0来复制。

现在,另外考虑一个例子,截断。
short s = 67;
char ch = s;
问题是,short比char 要多一个byte,那么赋值的话,是尽可能的接近吗?
NO,计算机不懂值得大小,只会单纯的copy,因此,截断后面的1byte赋值过去而已。

同样的,讲short赋值给int的时候也是这样处理的,高位全部copy为0,地位直接copy。而int赋值给short的时候,就是单纯的截断处理而已。
现在的问题来了,如果是负数呢?
short s =-1;
int i = s;
这个时候,前面的高位字节直接赋值为0的话,事实上数值的大小是变化的。因此计算机的做法是,拷贝符号位复制到高位。这样正数,就是拷贝的0,所以高位全为0,而负数的话高位1,拷贝的话,高位全1.最终保证i的值仍然是-1.

接下来学习float的表示。
我们可以自定义一种解释float的方法。权重依次降低,比如从2的31次方到2的0次方,变为2的29次方到2的-1次方,最开始的那一位表示为+/-符号位,这样就可以表示一定的浮点数,同样的,再次降低权重就可以表示更低的数据了。
但是,实际上计算机的表示不是这样的

采用了一种很奇怪的表示方法,即符号位(1位) + exp位(8位) + 浮点部分(23位)。
符号位表示政府,exp为8位的正数表示,浮点数表示0.XXXXX(0到1之间的数据)
最终浮点的值是,2的(exp - 127)次方 乘以 1.XXXX表示。


最后两个例子表示值拷贝的过程。
int i = 5;
float f = i;
输出的结果是f仍然是5,why? 因为不是bit copy,这里是直接进行赋值,而类型不同,因此会先计算出来值得带下,然后转换一种类型表示出来。
也就是把5的int类型表示为float的类型,bit pattern是完全发生了变化的。

另外一个例子。
int i = 37;
float f = *(float *) &i;
这种是把i的地址取出来,认为它表示的是float,因为(float *)的作用,然后解释为float类型输出它的值,注意的是,bit pattern并不会发生变化。

float f = 7.0;
short s = *(short *) &f;
由于是不同的类型,short只会截取自己那么大size的byte来进行翻译,所以float类型尽管4bytes,但是认为是short类型的话,仍然截取的是2bytes。
从而输出short的值应该是一个比较小的值。注意,bit pattern并不会改变,只是取出来地址而已。

总结的关键是:just copy bit pattern!

deercoder 2012-05-13 18:03 发表评论
]]>
斯坦福大学开放课程:编程范式学习笔记《一》http://www.cppblog.com/deercoder/archive/2012/04/29/173163.htmldeercoderdeercoderSun, 29 Apr 2012 10:26:00 GMThttp://www.cppblog.com/deercoder/archive/2012/04/29/173163.htmlhttp://www.cppblog.com/deercoder/comments/173163.htmlhttp://www.cppblog.com/deercoder/archive/2012/04/29/173163.html#Feedback0http://www.cppblog.com/deercoder/comments/commentRss/173163.htmlhttp://www.cppblog.com/deercoder/services/trackbacks/173163.html

编程范式(Paradigm) Lesson 1 读书笔记:

列举几种常见的编程语言(范式):

C

Assembly

C++

Concurrency programming(并行编程) (只是一种范式,而不是语言,可以使用C/C++实现并行编程)

Scheme

Python

 

C是面向过程,C++面向对象。C语言是函数调用函数,因此就像一个多级标题一样,通过函数A调用B 而函数B调用函数C,因此是有过程来决定一个函数的功能,我们首先看到的也就是一个过程(函数),通过指针来调用的。

 

C++是面向对象的,因此是通过“->”或者“.”来进行访问的,我们首先看到的就是指针或者引用,也就是一个完整的对象。


这系列课程会详细讲述底层是如何将C/C++编译为汇编语言的,会进行指针的详细讲解,让你使用到**&p-> i = 7 这些很复杂的用法,虽然过程可能有些头疼,但是对于了解底层很有帮助,让你知道崩溃的时候是为什么,而不是看着它崩溃。当然,这种用法还是不值得提倡的。通过这些课程,可以让你成为一个高级C/C++工程师。


汇编语言是很古老的,不会详细讲。有一个语言MITS,有点意思,可以研究下。会重点讲述CC++如何编译为obj文件,然后生成可执行的二进制文件,会发现原来CC++最终生成的二进制代码(01形式的),其实差不多。

C++去掉了面向对象的部分就是C,很多牛逼的工程师都倾向于使用C,尽管有很多优秀的语言在不同方面要比C优秀。

 

并行编程并不是真的并行。原来的语言,如C/C++,实际上都是执行完一条之后再执行,而并行编程则是并行执行(感觉上并行,实际上是交替执行,只是这个交替的频率很高,用户看不到这个交替的过程而已,从而认为是并行执行)。

并行编程很多地方使用不到,但是在网络编程上面很有用处。一个例子就是,两个用户同时从取款机上取一定的钱,比如余额为100,要保证他们不会同时取走,就是这样一个过程。将这一次操作成为事务(transaction)

 

Scheme是一个函数式的语言,和LISP有很深的渊源。可能大家不大了解,函数式的语言就是执行依赖于函数的返回结果。传统的语言有缺陷,比如C/C++,可能在传递指针的过程中,修改了这个结构体,然后再返回这个值,引起混乱。而函数式的语言就是,需要根据函数的返回值,决定下一步的执行。因此就不会出现这种问题。这门语言很有意思,大家可以学习一下,实际上比其他语言都要有意思(老师原语)。

 

Python是一门年轻语言,在Googlefacebook有相当多的工程师使用这门语言来进行开发,很适合网络编程,不要认为网络编程就是HTML,网页之类,动态网页还需要处理后台数据库的交互等问题。Python16~17年的历史,所以不会有想C/C++Java那样的缺陷,课程的最后有个大作业,做一个网页服务器,不会像Apache那么大,但是能够解析XML,HTML,后台进行处理,生成动态页面。Python有很多类库,是面向对象的语言,解释执行,可以一边写一边解释执行,还有很多函数库,可以借用Scheme的思想来做函数式编程,处理网络编程很有用户。

 

在过去课程最后是C++高级编程,后来就使用Java,,但是后来另外一个班教授的内容更深入,所以不交Java了。后来使用python,效果不错,课程最后是用python开发,会领会到其中美妙之处。

 

最后还会介绍一下其他语言和范式,不过你们在今后15年碰到的编程范式,我应该都见过(这个老师好自信啊)


最后课程结束,整个过程17分钟左右。下周会发超多讲义,并会让你们做一个C/C++中指针的底层原理的研究报告。Over。



deercoder 2012-04-29 18:26 发表评论
]]>