函数调用的汇编流程

测试环境:Windwos 7 + GCC 4.5.2 Target: mingw32

测试代码:

   1: code.c:
   2:  
   3: int accum = 0;
   4:  
   5: int sum(int x, int y)
   6: {
   7:     int t = x +y;
   8:     accum += t;
   9:     return t;
  10: }
  11:  
  12: main.c:
  13:  
  14: int main()
  15: {
  16:     return sum(1, 3);
  17: }

测试流程:

1. > gcc –O2 –S code.c

生成code.s 部分省略

   1: _sum: 
   2:     pushl    %ebp 
   3:     movl    %esp, %ebp 
   4:     movl    12(%ebp), %eax 
   5:     addl    8(%ebp), %eax 
   6:     addl    %eax, _accum 
   7:     leave 
   8:     ret 

可以注意到: sum函数首先保存ebp, 然后把当前esp 存入ebp, 因为esp寄存器存放当前栈顶指针,所以根据ebp的偏移获取参数x, y. (注意因为push ebp后,所以偏移为 12, 8, leave指令相当于 movl %ebp,%esp  popl %ebp, 对应的还有ENTER指令)

2 >gcc –O2 –c code.c

>objdump –d code.o

得到输出

   1: 00000000 <_sum>: 
   2:    0:   55                      push   %ebp 
   3:    1:   89 e5                   mov    %esp,%ebp 
   4:    3:   8b 45 0c                mov    0xc(%ebp),%eax 
   5:    6:   03 45 08                add    0x8(%ebp),%eax 
   6:    9:   01 05 00 00 00 00       add    %eax,0x0 
   7:    f:   c9                      leave 
   8:   10:   c3                      ret 
   9:   11:   90                      nop 
  10:   12:   90                      nop 
  11:   13:   90                      nop

注意这里 accum 的地方地址为全0,还没有最后确定。

3 >gcc –O2 –o prog code.o main.c

> objdump –d prog > tmp.txt

生成tmp.txt约1193行,查找到相关汇编码:

   1: 004013c0 <_sum>: 
   2:   4013c0:    55                       push   %ebp 
   3:   4013c1:    89 e5                    mov    %esp,%ebp 
   4:   4013c3:    8b 45 0c                 mov    0xc(%ebp),%eax 
   5:   4013c6:    03 45 08                 add    0x8(%ebp),%eax 
   6:   4013c9:    01 05 20 50 40 00        add    %eax,0x405020 
   7:   4013cf:    c9                       leave  
   8:   4013d0:    c3                       ret    
   9:   4013d1:    90                       nop 
  10:   4013d2:    90                       nop 
  11:   4013d3:    90                       nop
  12:  
  13: 004013d4 <_main>: 
  14:   4013d4:    55                       push   %ebp 
  15:   4013d5:    89 e5                    mov    %esp,%ebp 
  16:   4013d7:    83 e4 f0                 and    $0xfffffff0,%esp 
  17:   4013da:    83 ec 10                 sub    $0x10,%esp 
  18:   4013dd:    e8 3e 06 00 00           call   401a20 <___main> 
  19:   4013e2:    c7 44 24 04 03 00 00     movl   $0x3,0x4(%esp) 
  20:   4013e9:    00 
  21:   4013ea:    c7 04 24 01 00 00 00     movl   $0x1,(%esp) 
  22:   4013f1:    e8 ca ff ff ff           call   4013c0 <_sum> 
  23:   4013f6:    c9                       leave  
  24:   4013f7:    c3                       ret    
  25:     ...

发现: 这时候 accsum处已经填入了真实地址,不过main函数内的行为有点奇怪,首先保存ebp和esp, 然后清空低4位的%esp, 加上16,调用___main, 而___main处理信息有些奇怪,判断 %405060地址(由accsum的地址知道那应该是全局变量区附近)处内容是否为空来决定是否调用

401a3d:    eb 81                    jmp    4019c0 <___do_global_ctors>

不过与我们现在的讨论无关,先行跳过,之后到Linux上去调试看看。

之后的送入参数,奇怪的是 sub 0x10 把栈增加了16个长度后只使用最上面的8个。

最后执行调用,值得注意的是,无论是被调用函数还是调用函数都不再负责保持堆栈平衡,只要使用esp,ebp反复恢复上一次的栈顶情况就可以了。

发表在 C, 系统, 编程 | 标签为 , , | 留下评论

【转】堆和栈的区别——给初学者

http://www.chinaunix.net 作者:dreamice  发表于:2009-08-02 18:05:41

发表评论】 【查看原文】 【Linux讨论区】【关闭

一、预备知识—程序的内存分配 
由C/C++编译的程序占用的内存分为以下几个部分 
1、栈区(stack): 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 
2、堆区(heap): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。 
3、全局区(static): 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后有系统释放 。 
4、文字常量区: 常量字符串就是放在这里的, 程序结束后由系统释放。 
5、程序代码区: 存放函数体的二进制代码。 
Example: 
int a = 0; // 全局初始化区 
char *p1; // 全局未初始化区 
main() 

int b; // 栈 
char s[] = "abc"; // 栈 
char *p2; // 栈 
char *p3 = "123456"; // 123456\0在常量区,p3在栈上。 
static int c =0; // 全局(静态)初始化区 
p1 = (char *)malloc(10); 
p2 = (char *)malloc(20); // 分配得来得10和20字节的区域就在堆区。 
strcpy(p1, "123456"); // 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。 

二、堆和栈的理论知识 
2.1 申请方式 
栈: 由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间 
堆: 需要程序员自己申请,并指明大小,在c中malloc函数:如p1 = (char *)malloc(10); 在C++中用new运算符 如p2 = (char *)malloc(10); 但是注意p1、p2本身是在栈中的。 
2.2 申请后系统的响应 
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。 
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时, 会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。 
2.3 申请大小的限制 
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。 
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。 
2.4 申请效率的比较: 
栈:由系统自动分配,速度较快。但程序员是无法控制的。 
堆:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。 
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。 
2.5 堆和栈中的存储内容 
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。 
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。 
2.6 存取效率的比较 
char s1[] = "aaaaaaaaaaaaaaa"; 
char *s2 = "bbbbbbbbbbbbbbbbb"; 
aaaaaaaaaaa是在运行时刻赋值的; 
而bbbbbbbbbbb是在编译时就确定的; 
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。 
比如: 
#include 
void main() 

char a = 1; 
char c[] = "1234567890"; 
char *p ="1234567890"; 
a = c[1]; 
a = p[1]; 
return; 

对应的汇编代码 
10: a = c[1]; 
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh] 
0040106A 88 4D FC mov byte ptr [ebp-4],cl 
11: a = p[1]; 
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h] 
00401070 8A 42 01 mov al,byte ptr [edx+1] 
00401073 88 45 FC mov byte ptr [ebp-4],al 
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。 
2.7 小结: 
堆和栈的区别可以用如下的比喻来看出: 使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。 使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。 
还有就是函数调用时会在栈上有一系列的保留现场及传递参数的操作。栈的空间大小有限定,VC的缺省是2M。栈不够用的情况一般是程序中分配了大量数组和递归函数层次太深。有一点必须知道,当一个函数调用完返回后它会释放该函数中所有的栈空间。栈是由编译器自动管理的,不用你操心。堆是动态分配内存的,并且你可以分配使用很大的内存。但是用不好会产生内存泄漏。并且频繁地malloc和free会产生内存碎片(有点类似磁盘碎片),因为C分配动态内存时是寻找匹配的内存的。而用栈则不会产生碎片。在栈上存取数据比通过指针在堆上存取数据快些。一般大家说的堆栈和栈是一样的,就是栈(stack),而说堆时才是堆heap。栈是先入后出的,一般是由高地址向低地址生长。

发表在 C, 系统, 编程 | 标签为 , , | 留下评论

[摘抄]『煮酒论史』 [国学宗教]深入浅出说《心经》

晚上AE渲染的时候顺便看了下这篇文章。作者释戒悲写的挺好的  🙂

看的比较细,有点小困,先写两点。

1. 有求于佛,何谓解脱?

  求佛并不会得到世俗上的解脱,般若希望人们直面人生。 问题还是要靠自己解决的。

菩萨虽然大慈大悲救苦救难,但是如果铲这种事您还是找God Father比较专业。解铃还需系铃人。自己的问题终归还要自己解决。有了大智慧到彼岸当然就离开了此案的种种痛苦烦恼。但并不是说你真的把一个烂摊子甩下,然后自己划着小船去通吃岛了。烂摊子还是原来的烂摊子,你该住几平米的房子还住几平米。只不过原来的问题已经不成其为问题了。按照更佛教一点的说法应该是本来就没有实际的问题,只是你自生分辨,认为有很多的问题。

2. 万物皆空?

   总的来说,万物自性空,即最开始什么都不是,却又是最细小的存在。

   因缘起而不断合和而成,这时候我们为他们加上了相,成为了我们所见的万物,都是缘起合和后在我们心理的投影。

“色即是空”的“空”也是指“缘起而无自性”。就是所谓的“自性空”。无自性可以用拳头来做比喻。世界上真的有一种实在的东西叫做拳头吗?其实没有。只不过是当我们把手指攥在一起的时候我们称它为拳头。这就是自性空。它不是说你伸出来的拳头本身是不存在的。而是说拳头只是一种“相”,是由因缘和合而成的。所以没有实在的“自性”。不存在一个绝对意义上的拳头,所以是“假名有”;但由于依五蕴和合而生,又是“自性空”。
同样的可以说海浪。我们如果说“空”就认为世界上不存在海浪,那就是执着于实空,是“恶趣空”,是不正确的观点。如果认为有一个实有自性的海浪存在,那就是执着于实有,也是不正确的观点。离开实有和实空两边的偏见,认识到浪是由水的形态幻化而出,既非实有也非实无而是“性空而假名有”才是中道实相。
同理,刚才我们说的那个瓷杯子,你把它当成“杯具”还是当成“洗具”也都是你的自生分辨而已了。

发表在 唠叨, 网摘, 网络 | 标签为 , | 留下评论

罗马字母

你可能经常看到罗马数字,即使你没有意识到他们。

你可能曾经在老电影或者电视中看到他们(“版权所有 MCMXLVI” 而不是 “版权所有1946”),或者在某图书馆或某大学的贡献墙上看到他们(“成立于 MDCCCLXXXVIII”而不是“成立于1888”)。

你也可能在某些文献的大纲或者目录上看到他们。这是一个表示数字的系统,他能够真正回溯到远古的罗马帝国(因此而得名)。

在罗马数字中,利用7个不同字母进行重复或者组合来表达各式各样的数字。

  • I = 1
  • V = 5
  • X = 10
  • L = 50
  • C = 100
  • D = 500
  • M = 1000

下面是关于构造罗马数字的一些通用的规则的介绍:

  • 字符是叠加的。 I表示1, II表示2, 而III表示3. VI 表示 6 (字面上为逐字符相加, “51”), VII 表示 7, VIII 表示 8.
  • 能够被10整除的字符(I, X, C, 和 M)至多可以重复三次. 对于4, 你则需要利用下一个最大的能够被5整除的字符进行减操作得到,你不能把4 表示成 IIII; 而应表示为 IV (比“51”)。数字40写成XL (比5010), 41 写成 XLI, 42 写成 XLII, 43 写成 XLIII, 而 44 写成 XLIV (比5010, 然后比51).
  • 类似的,对于数字 9,你必须利用下一个能够被10整除的字符进行减操作得到: 8 表示为 VIII, 而 9 则表示为 IX (比101), 而不是 VIIII (因为字符I 不能连续重复四次)。数字90 表示为 XC, 900 表示为 CM.
  • 被5整除的字符不能重复。数字10 常表示为X, 而从来不用VV来表示。数字100常表示为C, 也从来不表示为 LL.
  • 罗马数字经常从高位到低位书写,从左到右阅读,因此不同顺序的字符意义大不相同。DC 表示 600; 而CD 是一个完全不同的数字(为400, 也就是比500100). CI 表示 101; 而IC 甚至不是一个合法的罗马字母(因为你不能直接从数字100减去1; 比需要写成XCIX, 意思是 比10010, 然后加上数字9,也就是比 101的数字).

发表在 唠叨 | 标签为 | 2条评论

.NET下的SQL操作

   这段时间研究了WCF和C#的ADO.NET, SQL操作,LINQ查询虽然方便,但是对于刚开始学习,特别是没有好的指导老师的人来说,还是不推荐大范围使用。所以这里还是使用常规的数据库操作方法。

  1. 连接数据库,其实作用并不大,好处在于自动生成ConnectString。

image

右键点击 Data Connections,选择Add Connection, 开始添加数据库,注意\SQLEXPRESS,另外如果要远程链接,现在数据库中注册有权限的帐号,然后登陆。

2. 连接ADO.NET,作用在于会自动生成对于的数据结构类,方便我们的操作,也就不用每个表都要创建类了。

  右键点击Project, 添加Data页的ADO.NET,同样也是配置数据库的连接。

image

3. 在我们第一次连接的数据库Server属性中有一个Connection String, Copy之。

        public int SQL_connect()
        {
            SQLConnect = new SqlConnection();

            SQLConnect.ConnectionString = "Data Source=WIN-01L9BROHARG\\SQLEXPRESS;Initial Catalog=TutorNet;Persist Security Info=True;User ID=ctq;Password=ctqmumu";

            SQLConnect.Open();

            if (SQLConnect.State != System.Data.ConnectionState.Open)
            {
                //TODO output error
                return ErrorReturn;
            }

            return 0;
        }

有返回值的数据库操作

 
 public List SQL_select_user_By_ID(string UID)
        {
            if (SQLConnect == null) SQL_connect();

            List listUser = new List();

            try
            {
                SQLCommand = new SqlCommand();

                SQLCommand.Connection = SQLConnect;

                SQLCommand.CommandText = "SELECT * FROM UserTable WHERE UID = '" + UID + "';";

                SQLCommand.CommandType = CommandType.Text;

                SqlDataReader reader = SQLCommand.ExecuteReader();

                if (reader.HasRows)
                {
                    while (reader.Read())
                    {
                        listUser.Add(new UserTable
                        {
                            UID = (int)reader[0],
                            Password = (string)reader[1],
                            Name = (string)reader[2],
                            PhoneNumber = (reader[3] is System.DBNull) ? null : (string)reader[3],
                            Email = (string)reader[4],
                            ST = (reader[5] is System.DBNull) ? 0 : (int)reader[5],
                            Point = (reader[6] is System.DBNull) ? 0 : (int)reader[6],
                            PopIndex = (reader[7] is System.DBNull) ? 0 : (int)reader[7],
                            Login = (int)reader[8]
                        });
                    }
                }
                else
                {
                    //MessageBox.Show("No rows found.");
                }
                reader.Close();

            }
            catch (Exception e)
            {

                return null; // 因为在WCF部署后无法正确处理,所以return null
            }

            return listUser;
        }

无返回类型的操作,返回为bool型,即插入,更新和删除操作

    public bool SQL_UPDATE_UserTable_Login(string UID, int val)
        {
            Int32 recordsAffected = 0;
            try
            {

                string queryString = "UPDATE UserTable SET Login=" + val + " WHERE UID=" + UID + " ;";

                SqlCommand command = new SqlCommand(queryString, SQLConnect);
                recordsAffected = command.ExecuteNonQuery();

            }
            catch (Exception e)
            {
                return false;
            }

            return true;
        }
发表在 C#, 编程 | 标签为 , , | 留下评论

[无题]有时候静静的听歌也好

发表在 唠叨 | 标签为 , | 留下评论

如何增加WCF连接的最大传输长度

刚刚在调试WCF传输Byte[]格式图片时,遇到了WCF默认限制传输不能超过16K的问题。

最开始显示的提示是:

   Bad request , Code:  400

想了一会,觉得是IIS7的配置问题,于是查看了一下默认配置,打开IIS的“失败请求跟踪规则”

image

配置跟踪400错误,之后再运行,显示错误:

“The formatter threw an exception while trying to deserialize the message: Error in deserializing body of request message for operation ‘XXX’. The maximum array length quota (16384) has been exceeded while reading XML data. This quota may be increased by changing the MaxArrayLength property on the XmlDictionaryReaderQuotas object used when creating the XML reader. Line 1, position 39987.”

现在看到错误了,可以去Google一下,查看到具体信息,需要调整:

这篇文章写的很详细:How to Increase the MaxArrayLength in a WCF Component

注意的情况有:1. 分别更新客服端和服务端的.config文件 2. 推荐使用工具里的“WCF服务配置编辑器”

最后成功的情况:

image

发表在 C#, 编程 | 标签为 , , | 留下评论

4月15日

昨晚过错了帮小葛的签到,愧疚。

因此晚上和他们聊了一会,体会到很多,这也是我要自己作出抉择的地方吧。

感性和理性,如果一直这样交错占据主动,以后会出大问题的。

需要跟多时间好好想想,不要伤害了身边的人。

发表在 唠叨 | 标签为 | 留下评论

[转载]How To Read C Declarations

查看C++书的时候,碰巧看到一个函数声明

bool Sales_item::same_isbn   (const Sales_item * const this, const Sales_item &rhs) const

意思是:当一个函数被声明为常量成员函数(const member function),

这时隐藏的this指针被标记成  const Sales_item * const this, 是的,这里有两个const。

下面的这篇文章教导人们如何快速的阅读 C Declarations。

给出三个实例。 断言为 *a = b 是合法的。

  const int * a = &b;  // 假

  int const * a = &b; // 假

  int * const  a = &b; // 真

  ZZ from http://blogold.chinaunix.net/u1/36290/showart_443799.html

    Even experienced C programmers have difficulty reading declarations that go beyond simple arrays and pointers. For example, is the following an array of pointers or a pointer to an array?

int *a[10];

What the heck does the following mean?

int (*(*vtable)[])();

Naturally, it’s a pointer to an array of pointers to functions returning integers. 😉

This short article tells you how to read any C declaration correctly using a very simple technique. I am 99% certain that I read this in a book in the late 1980s, but I can’t remember where. I doubt that I discovered this on my own (even though I’ve always been delighted by computer language structure and esoterica). I do remember, however, building a simple program that would translate any declaration into English.

The golden rule

The rule goes like this:

"Start at the variable name (or innermost construct if no identifier is present. Look right without jumping over a right parenthesis; say what you see. Look left again without jumping over a parenthesis; say what you see. Jump out a level of parentheses if any. Look right; say what you see. Look left; say what you see. Continue in this manner until you say the variable type or return type."

这条规律是这样的:

     从变量名(或者是一个最深处的结构)开始,先看他右边一个命名,不要越过右括号(即遇到括号放弃),说出你看到的,然后再看他的左边一个命名,同样不越过当前层左括号,说出你看到的,一次循环后,如果这次遇到了阻挡,那么跳出这对括号;依次反复,直到读完所有的变量。

   我或许可以模糊的告诉你大意,但是我认为更好的办法就是阅读原文(直接看英语更利于你理解)。起码该自己阅读下面的话:

The degenerate case is:

int i;

Starting at i, you look right and find nothing. You look left and find the type int, which you say. Done.

Ok, now a more complicated one:

int *a[3];

Start at a. Look right, say array of size 3. Look left and say pointer. Look right and see nothing. Look left and say int. All together you say a is an array of size 3 pointers to int.

Adding parentheses is when it gets weird:

int (*a)[3];

The parentheses change the order just like in an expression. When you look right after a, you see the right parenthesis, which you cannot jump over until you look left. Hence, you would say a is a pointer to an array of 3 ints.

Function pointers

The C "forward" declaration:

extern int foo();

just says that foo is a function returning int. This follows the same pattern for reading declarators as you saw in previous section. Start at foo and look right. You see () so say function. You look left and see int. Say int.

Now, try this one:

extern int *foo();

Yep, you say foo is a function returning a pointer to int.

Now for the big leap. Just like we can make a pointer to an int or whatever, let’s make a pointer to a function. In this case, we can drop the extern as it’s no longer a function forward reference, but a data variable declaration. Here is the basic pattern for function pointer:

int (*foo)();

You start at foo and see nothing to the right. So, to the left, you say pointer. Then to the right outside you see function. Then left you see int. So you say foo is a pointer to a function returning int.

Combinations

Here is an array of pointers to functions returning int, which we’ll need for vtables below:

int (*Object_vtable[])();

You need one last, incredibly bizarre declaration, for the lab:

int (*(*vtable)[])();

This is the pointer to the vtable you will need in each "object" you define.

This pointer to a vtable is set to the address of a vtable; for example, &Truck_vtable.

Summary

The following examples summarize the cases needed for building virtual tables ala C++ to implement polymorphism (like the original cfront C++ to C translator).

int *ptr_to_int;
int *func_returning_ptr_to_int();
int (*ptr_to_func_returning_int)();
int (*array_of_ptr_to_func_returning_int[])();
int (*(*ptr_to_an_array_of_ptr_to_func_returning_int)[])();
 
 
发表在 C, C++, 编程, 网摘, 网络, 读书 | 标签为 , | 留下评论

【整理】接口,抽象类,继承和多态

首先是基本信息,面向对象编程有三个特征,即封装、继承和多态。

封装隐藏了类的内部实现机制,从而可以在不影响使用者的前提下改变类的内部结构,同时保护了数据。

继承是为了重用父类代码,同时为实现多态性作准备。

那么什么是多态呢?多态的使用是为了使得程序开发时,控制代码的耦合性,使得开发,维护,修改的代价保持在较低水平。

当当这样说肯定是不足以说明的,先来看下面的例子。

Java和C++中的描述略有不同,因为C++允许多继承,而Java是单继承。

这里先给出Java多态的测试:


public class Father {
	
	public int test = 1;
	
	public void print() {
		
		System.out.println("Father have:" + test);
		
	}	
	
	public static void main(String[] args){
		
		Father son = new Son();
		
		son.print();
		
		System.out.println("Now have:"+son.test);
		
		Son _son = (Son)son;
		
		System.out.println("Now have:"+_son.test);
			
	}

}
public class Son extends Father {
	
	public int test = 2;
	
	public void print(){
		
		System.out.println("Son have:" + test);
		
	}	
}

/** 输出 **/

Son have:2
Now have:1
Now have:2

了解了基本的多态方法后,就会遇到一个问题,什么时候该使用多态?

这篇文章介绍了如何避免被你的项目经理”消灭”的方法

例如我们有一位用户的测速需求,他现在使用的是JD,而过了一段时间改用Benz,如果高耦合的开发很可能要重新添加修改大量代码。所以引进了多态。这时可以回过头看上面的话:

多态的使用是为了使得程序开发时,控制代码的耦合性,使得开发,维护,修改的代价保持在较低水平。

回头找来C++的书,写一写C++的多态代码

发表在 C++, JAVA, 编程 | 标签为 | 留下评论