阳光男孩

Never give up!

Entries for the ‘C/C++’ Category

Java C++ 两大语言语言在作用域上的差异

1顶一下Java语言与C++语言是目前最流行的编程语言。两者的编程思想虽然有一定的共同性,但是在很多方面仍然存在着比较大的差异。如两者在作用域上仍然存在着很大的差异。下面笔者就分析一下这两门语言在作用域上的差异,以帮助大家进一步认识Java语言的优势。 差异一:变量作用域的不同。 如下图,这段程序代码是符合C++语言的语法要求的。其可以在C语言下正常运行。但是其在Java语言平台下编译的...[阅读全文]

1
顶一下

Java语言与C++语言是目前最流行的编程语言。两者的编程思想虽然有一定的共同性,但是在很多方面仍然存在着比较大的差异。如两者在作用域上仍然存在着很大的差异。下面笔者就分析一下这两门语言在作用域上的差异,以帮助大家进一步认识Java语言的优势。

差异一:变量作用域的不同。
如下图,这段程序代码是符合C++语言的语法要求的。其可以在C语言下正常运行。但是其在Java语言平台下编译的时候,就会被告知有错误。其格式、关键字上面都没有错误。那么错误到底是这么呢?这就关系到变量的作用域。

{
float y=3.15
{
float y=3.15
}
}
作用域就决定了其定义的变量名的可见性与生命周期。在C++语言(包括其衍生出来的其他语言)与Java语言中,都是用一定花括号来代表一个作用域的。如上面的代码,就表示有两个作用域。外面一对花括号代表一级作用域;里面一对花括号代表二级作用域,依次类推。通常情况下,一级作用域中定义的变量,对其下级作用率都是有效的。也就是说,其下级作用域可以直接引用上级作用域中定义的变量。但是在二级作用域中,可以更改一级作用率中设置的变量值,不过这个更改只在二级作用域内部有效。如上面这个代码,在二级作用域中可以再定义一个y变量,重新赋值。注意,其实在二级作用域中定义的变量与一级作用域中定义的变量,虽然名字相同,但是他们不是同一个变量。如果此时在一级作用域外,有其他代码引用这个y变量的话,则其的值仍然是3.14,而不会是3.15。也就是说,在二级作用域中定义的变量,只在其内部有效。对于上级作用域是没有丝毫影响的。也就是说,在作用域中定义的变量只在其作用域内有效。出了其作用域外,其内部设置的变量就全部无效了。

这个作用域的设置其实Java语言与C++语言是类似的。但是在细节上Java语言又多了一条限制。如上表的代码所示,虽然在上面的代码中变量y在两个不同的作用域中定义,照理来说是两者是互不干涉,可以共存。但是在Java编译器中,是不允许有这种情况存在的。把上面这个代码在Java编译器中编译的话,编译器会通知程序开发人员,说这个变量y已经定义过。然后编译会以错误告终。虽然在C++语言中在不同级别的作用域中定义名字相同的变量是允许的,而在Java语言中则不行,这并不代表在变量的作用域上有所不同。其实从本质上来说,两者变量的作用域是相同的。只是对于Java语言来说,其又多加了一条限制。在Java语言中,即使作用域不同,其定义的变量名字也不能够相同。这主要是为了提高Java代码的可读性,防止混淆才定义了这条规则。

差异二:对象作用域的差异。
Java语言与C++语言一样,都是面向对象的语言。不过两者在实现机制上有很大的不同。就拿对象的作用域来说,就有很大的差异。这也导致了两个面向对象的语言在实现细节上的巨大差异。

首先Java程序员需要明白的是,Java对象作用域与变量的作用域是不同的。如上面的分析,变量的作用域只在作用域内部有效。如在二级作用域内定义的变量,超出了二级作用域,那么就无效了。但是对象则不同,其可以存在于作用域之外。如现在在某个作用域内定义了一个name_full对象。当脱离这个作用域的时候,这个对象的引用是消失了。但是刚才创建的这个对象仍然实实在在的保存在内存中。在Java程序的运行过程中,只要通过传递或者复制对象引用的手段,那么在其他作用域内仍然可以访问这个对象。也就是说,只要我们有这个需要,那么在某个作用域内创建的对象其会一直存在并可以在作用域外的其他任何一个地方进行访问。当然前提是要通过复制或者传递等手段把对象引用传递到其他的作用域中。这就是Java对象与Java变量在作用域上最大的不同。
其次,Java对象与C++语言的作用域有很大的不同。其实C++语言中的对象跟变量的作用域到是很类似的。在C++语言中一旦使用完对象之后,就必须把这个对象销毁掉。说的确切一点,就是要在作用域内把使用完的对象所占的内存空间释放掉。否则的话,如果在作用于外部,由于已经失去了对这个对象的引用,为此这个对象就好像成为了太空中的一个人,无法再对其进行任何的操作,只要任其自生自灭。为此对于C++语言来说,程序员很难在脱离作用域外后,确保在需要调用对象时,仍然可以访问这个对象。这也正是C++语言开发过程中最让人头疼的问题。因为需要手工来销毁对象。万一对象所占用的内存空间没有别及时释放的话,那么对于应用程序的安全与性能都会产生很大的影响。

在Java程序中,这个作用域外的对象最终有两个去向。首先,可以通过复制或者传递,在作用域外部仍然可以访问这个对象。其次,就是销毁对象。不过我们不用通过代码来销毁这个以前创建的对象。因为在Java语言中有一种叫做垃圾回收器的处理机制,其可以用来动态监视New关键字创建的所有对象,并根据一定的规则来判断哪些对象不会再被引用。如果其判断某个对象不再被引用话,则会自动释放这些对象所占用的内存空间,以供其他新的对象所使用。我们程序开发人员只管创建对象即可,而不用去担心什么时候去销毁对象。为此,这就可以消除C++语言面临的内存溢出问题。这个内存溢出问题就是因为程序开发人员用完对象后忘记销毁所造成的。

由于在Java程序开发中,我们开发人员不用关心对象的销毁问题,为此可以更多的精力放在代码的优化上。而不像C++语言那样,要把这个对象销毁问题当作头件大事来对待。虽然如此,不过Java程序员也不能够掉以轻心。特别是当我们在离开某个作用域后还需要访问这个对象的时候,一定要记得通过复制或者传递等手段把对象引用传递给其他作用域。否则的话,即使这个对象没有消亡,还实际存储在内存中,但是也会因为缺少了引用而无法访问他们。为此在跨作用域引用对象的时候,这个引用的复制与传递千万不能够忘了。

如果不幸忘了的话,那么在作用域外的代码就无法再访问这个对象。不过这个对象在一定时候会被垃圾回收器回收了。被释放了的内存空间就可以被重复使用,从而防止内存溢出的问题发生。

总之,当Java程序员在开发应用程序的时候,这个Java对象与Java变量作用域的差异,以及Java对象与其他语言对象作用域的差异,一定要了然于胸。这有助于Java程序员能够更好的利用这个Java对象。另外笔者不厌其烦的再强调一遍,默认情况下脱离了某个作用域之后,对象就失去了引用无法访问。如果要在作用域再访问这个对象的时候,则需要及时把这个引用复制或者传递出来。否则的话,作用域外的代码是无法再操作这个对象。

Comments (45)

代码注释的13个技巧

1顶一下下面的13个技巧向你展示如何添加代码注释,这些技巧都很容易理解和记忆。 1. 逐层注释 为每个代码块添加注释,并在每一层使用统一的注释方法和风格。例如: 针对每个类:包括摘要信息、作者信息、以及最近修改日期等 针对每个方法:包括用途、功能、参数和返回值等 在团队工作中,采用标准化的注释尤为重要。当然,使用注释规范和工具(例如C#里的XML,Java里的Javadoc)可以更好的推动...[阅读全文]

1
顶一下

下面的13个技巧向你展示如何添加代码注释,这些技巧都很容易理解和记忆。
1. 逐层注释
为每个代码块添加注释,并在每一层使用统一的注释方法和风格。例如:

针对每个类:包括摘要信息、作者信息、以及最近修改日期等
针对每个方法:包括用途、功能、参数和返回值等

在团队工作中,采用标准化的注释尤为重要。当然,使用注释规范和工具(例如C#里的XML,Java里的Javadoc)可以更好的推动注释工作完成得更好。
2. 使用分段注释
如果有多个代码块,而每个代码块完成一个单一任务,则在每个代码块前添加一个注释来向读者说明这段代码的功能。例子如下:

// Check that all data records
// are correct
foreach (Record record in records)
{
if (rec.checkStatus()==Status.OK)
{
. . .
}
}
// Now we begin to perform
// transactions
Context ctx = new ApplicationContext();
ctx.BeginTransaction();
. . .
3. 在代码行后添加注释
如果多行代码的每行都要添加注释,则在每行代码后添加该行的注释,这将很容易理解。例如:

const MAX_ITEMS = 10; // maximum number of packets
const MASK = 0x1F;    // mask bit TCP

在分隔代码和注释时,有的开发者使用tab键,而另一些则使用空格键。然而由于tab键在各编辑器和IDE工具之间的表现不一致,因此最好的方法还是使用空格键。
4. 不要侮辱读者的智慧
避免以下显而易见的注释:

if (a == 5)      // if a equals 5
counter = 0; // set the counter to zero

写这些无用的注释会浪费你的时间,并将转移读者对该代码细节的理解。
5. 礼貌点
避免粗鲁的注释,如:“注意,愚蠢的使用者才会输入一个负数”或“刚修复的这个问题出于最初的无能开发者之手”。这样的注释能够反映到它的作者是多么的拙劣,你也永远不知道谁将会阅读这些注释,可能是:你的老板,客户,或者是你刚才侮辱过的无能开发者。
6. 关注要点
不要写过多的需要转意且不易理解的注释。避免ASCII艺术,搞笑,诗情画意,hyperverbosity的注释。简而言之,保持注释简单直接。
7. 使用一致的注释风格
一些人坚信注释应该写到能被非编程者理解的程度。而其他的人则认为注释只要能被开发人员理解就行了。无论如何,Successful Strategies for Commenting Code已经规定和阐述了注释的一致性和针对的读者。就个人而言,我怀疑大部分非编程人员将会去阅读代码,因此注释应该是针对其他的开发者而言。
8. 使用特有的标签
在一个团队工作中工作时,为了便于与其它程序员沟通,应该采用一致的标签集进行注释。例如,在很多团队中用TODO标签表示该代码段还需要额外的工作。

int Estimate(int x, int y)
{
// TODO: implement the calculations
return 0;
}

注释标签切忌不要用于解释代码,它只是引起注意或传递信息。如果你使用这个技巧,记得追踪并确认这些信息所表示的是什么。
9. 在代码时添加注释
在写代码时就添加注释,这时在你脑海里的是清晰完整的思路。如果在代码最后再添加同样注释,它将多花费你一倍的时间。而“我没有时间写注释”,“我很忙”和“项目已经延期了”这都是不愿写注释而找的借口。一些开发者觉得应该write comments before code,用于理清头绪。例如:
public void ProcessOrder()
{
// Make sure the products are available
// Check that the customer is valid
// Send the order to the store
// Generate bill
}
10. 为自己注释代码
当注释代码时,要考虑到不仅将来维护你代码的开发人员要看,而且你自己也可能要看。用Phil Haack大师的话来说就是:“一旦一行代码显示屏幕上,你也就成了这段代码的维护者”。因此,对于我们写得好(差)的注释而言,我们将是第一个受益者(受害者)。
11. 同时更新代码和注释
如果注释没有跟随代码的变化而变化,及时是正确的注释也没有用。代码和注释应该同步变化,否则这样的注释将对维护你代码的开发者带来更大的困难。使用重构工具时应特别注意,它只会自动更新代码而不会修改注释,因此应该立即停止使用重构工具。
12. 注释的黄金规则:易读的代码
对于开发者的一个基本原则就是:让你的代码为己解释。虽然有些人怀疑这会让那些不愿意写注释的开发者钻空子,不过这样的代码真的会使你容易理解,还不需要额外维护注释。例如在Fluid Interfaces文章里向你展示的代码一样:

Calculator calc = new Calculator();
calc.Set(0);
calc.Add(10);
calc.Multiply(2);
calc.Subtract(4);
Console.WriteLine( “Result: {0}”, calc.Get() );

在这个例子中,注释是不需要的,否则可能就违反了技巧4。为了使代码更容易理解,你可以考虑使用适当的名字(Ottinger’s Rules里讲解得相当好),确保正确的缩进,并且采用coding style guides,违背这个技巧可能的结果就像是注释在为不好的代码apologize。
13. 与同事分享技巧
虽然技巧10已经向我们表明了我们是如何从好的注释中直接受益,这些技巧将让所有开发者受益,特别是团队中一起工作的同事。因此,为了编写出更容易理解和维护的代码,尝试自由的和你的同事分享这些注释技巧。

同类型的文章有:

Comments (278)

重叠IO overlapped I/O运用详解

1顶一下I/O设备处理必然让主程序停下来干等I/O的完成, 对这个问题有 方法一:使用另一个线程进行I/O。这个方案可行,但是麻烦。                即 CreateThread(…………);创建一个子线程做其他事情。      Readfile(^…………);阻塞方式读数据。 方法二:使用overlapped I/O。 overlapped I/O是WIN32的一项技术, 你可以要求操作系统为你传送数据,并且在传送完毕时通知你...[阅读全文]

1
顶一下

I/O设备处理必然让主程序停下来干等I/O的完成,
对这个问题有

方法一:使用另一个线程进行I/O。这个方案可行,但是麻烦。                即 CreateThread(…………);创建一个子线程做其他事情。      Readfile(^…………);阻塞方式读数据。

方法二:使用overlapped I/O。
overlapped I/O是WIN32的一项技术, 你可以要求操作系统为你传送数据,并且在传送完毕时通知你。这项技术使你的程序在I/O进行过程中仍然能够继续处理事务。事实上,操作系统内部正是以线程来I/O完成overlapped I/O。你可以获得线程的所有利益,而不需付出什么痛苦的代价

怎样使用overlapped I/O:

进行I/O操作时,指定overlapped方式
使用CreateFile (),将其第6个参数指定为FILE_FLAG_OVERLAPPED,
就是准备使用overlapped的方式构造或打开文件;
如果采用 overlapped,那么ReadFile()、WriteFile()的第5个参数必须提供一个指针,
指向一个OVERLAPPED结构。 OVERLAPPED用于记录了当前正在操作的文件一些相关信息。

//功能:从指定文件的1500位置读入300个字节
int main()
{
BOOL rc;
HANDLE hFile;
DWORD numread;
OVERLAPPED overlap;
char buf[512];
char szPath=”c:\\xxxx\xxxx”;
hFile = CreateFile( szPath,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, // 以overlapped打开文件
NULL
);

// OVERLAPPED结构实始化为0
memset(&overlap, 0, sizeof(overlap));
//指定文件位置是1500;
overlap.Offset = 1500;

rc = ReadFile(hFile,buf,300,&numread,&overlap);
//因为是overlapped操作,ReadFile会将读文件请求放入读队列之后立即返回(false),而不会等到文件读完才返回(true)
if (rc)
{

…………此处即得到数据了。
//文件真是被读完了,rc为true
// 或当数据被放入cache中,或操作系统认为它可以很快速地取得数据,rc为true
}
else
{
if (GetLastError() == ERROR_IO_PENDING)
{//当错误是ERROR_IO_PENDING,那意味着读文件的操作还在进行中
//等候,直到文件读完
WaitForSingleObject(hFile, INFINITE);
rc = GetOverlappedResult(hFile,&overlap,&numread,FALSE);
//上面二条语句完成的功能与下面一条语句的功能等价:

一只阻塞等到得到数据才继续下面。
// GetOverlappedResult(hFile,&overlap,&numread,TRUE);
}
else
{
//出错了
}
}
CloseHandle(hFile);
return EXIT_SUCCESS;
}

在实际工作中,若有几个操作同一个文件时,
怎么办?我们可以利用OVERLAPPED结构中提供的event来解决上面遇到的问题。
注意,你所使用的event对象必须是一个MANUAL型的;否则,可能产生竞争条件。
int main()
{
int i;
BOOL rc;
char szPath=”x:\\xxxx\xxxx”;
// 以overlapped的方式打开文件
ghFile = CreateFile( szPath,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL
);
for (i=0; i<MAX_REQUESTS; i++)   requests 同时有N个同时读取文件
{
//将同一文件按几个部分按overlapped方式同时读
//注意看QueueRequest函数是如何运做的,每次读16384个块
QueueRequest(i, i*16384, READ_SIZE);
}
// 等候所有操作结束;
//隐含条件:当一个操作完成时,其对应的event对象会被激活
WaitForMultipleObjects(MAX_REQUESTS, ghEvents, TRUE, INFINITE);
// 收尾操作
for (i=0; i<MAX_REQUESTS; i++)
{
DWORD dwNumread;
rc = GetOverlappedResult(
ghFile,
&gOverlapped[i],
&dwNumread,
FALSE
);
CloseHandle(gOverlapped[i].hEvent);
}
CloseHandle(ghFile);
return EXIT_SUCCESS;
}

//当读操作完成以后,gOverlapped[nIndex].hEvent会系统被激发
int QueueRequest(int nIndex, DWORD dwLocation, DWORD dwAmount)
{
//构造一个MANUAL型的event对象
ghEvents[nIndex] = CreateEvent(NULL, TRUE, FALSE, NULL);
//将此event对象置入OVERLAPPED结构
gOverlapped[nIndex].hEvent = ghEvents[nIndex];

每个重叠对象对应一个事件。
gOverlapped[nIndex].Offset = dwLocation;
for (i=0; i<MAX_TRY_COUNT; i++) //尝试几次。
{
//文件ghFile唯一
rc = ReadFile(ghFile, gBuffers[nIndex],&dwNumread,&gOverlapped[nIndex]);
if (rc) 如果立刻读到数据则返回真
return TRUE;
err = GetLastError();
if (err == ERROR_IO_PENDING)
{
//当错误是ERROR_IO_PENDING,那意味着读文件的操作还在进行中
return TRUE;
}
// 处理一些可恢复的错误
if ( err == ERROR_INVALID_USER_BUFFER ||
err == ERROR_NOT_ENOUGH_QUOTA ||
err == ERROR_NOT_ENOUGH_MEMORY )
{
sleep(50);
continue;//重试
}
// 如果GetLastError()返回的不是以上列出的错误,放弃
break;
}

return -1;

}

程序流程:

1: N个用户同时读取一个文件的各个部分,且每个用户对应一个重叠对象和事件。

2:调用WaitForMultipleObjects(MAX_REQUESTS, ghEvents, TRUE, INFINITE) 当任何一个用户的读操作完成时,函数停止阻塞。并且ghEvents中对应于的读取数据完毕的用户的事件被激活。

3: 调用GetOverlappedResult 取得读取数据完毕的用户编号。

Comments (72)

从C++转到Java需注意的地方

1顶一下1.Java在虚拟机上运行 Java源代码并不是被编译成为普通的机器代码。而是被翻译成为虚拟机可以执行的代码。一个Java解释器最终执行这些代码。这其中没有连接的过程;解释在需要的时候动态的加载一些类; 2.Java是完全面向对象的 Java是一种完全面向对象的语言。这意味着你对任何一个Java对象所做的动作都是通过一个方法实现的。第一点就是,再也没有没有主函数这样的 孤立的东西了。取而代...[阅读全文]

1
顶一下

1.Java在虚拟机上运行

Java源代码并不是被编译成为普通的机器代码。而是被翻译成为虚拟机可以执行的代码。一个Java解释器最终执行这些代码。这其中没有连接的过程;解释在需要的时候动态的加载一些类;

2.Java是完全面向对象的

Java是一种完全面向对象的语言。这意味着你对任何一个Java对象所做的动作都是通过一个方法实现的。第一点就是,再也没有没有主函数这样的 孤立的东西了。取而代之的是,你必须开始用一个对象的看法看待一个程序,一个类的对象。但是这个对象又什么对象呢?大多数Java程序只是简单的通过继承 Java基础类Object来实现所需要的东西,但是你可以通过创建程序基础类用于多个特性相似的应用程序来节省时间。

严格的面向对象的规定意味着理用原有的C/C++代码不可以直接不加改动的使用;系统调用也是这样的。C++中,你可以通过在C++正常的命名空间外声明extern”C”来使用原有的C的过程调用,包括系统调用。

在Java中,只有一个类似的安全回溯的方法,但是并不是十分简单的方法。你必须定义一个本地方法,其目的是为C语言提供接口,然后提供连接的介 质。Java环境提供了完成这种任务的工具,但是整个过程和C++中提供的extern比微不足道,完成使用C++类的过程则更加复杂,因为这样会引入对 C的借口和C函数和C++成员函数的问题。

幸运的是,许多常用的系统实用工具函数已经在系统类中的方法中提供出来,但是这些明显没有包含经过许多年来你所创建的那些类和过程。所以,在你需要的时候你应该去钻研一下。

3.Java中没有独立的头文件

在Java中,关于类的一切东西都被放到一个单独的文件中。方法的位置只可能在一个地方出现,一个方法的实现必须在它的定义过程中同时进行。这样 做得优点是在实现程序的时候不容易因为文件的非同步错误而失败,或者获取到一个没有实现的声明。类的声明可以被Java解释器利用甚至是从一个编译过的单 元中获取,所以不再需要有头文件,只要有编译过的文件。

这样做的缺点与我们编程的过程有关。许多C++程序员喜欢用头文件来代替文档。要看一个成员函数的接口参数,只需要看头文件中的声明即可。你可以 经常的看头文件即可了解怎样去使用这个类。在Java中,没有这样的总结。因为实现类方法的代码必须在方法定义的时候出现,而且,对于一个单独的函数的代 码来说就经常占据了一整页乃至更多。这样,很难通过看Java的代码就初步了解类是怎样使用的。你必须为你需要的类准备足够多的文档。不言而喻,再处理非 商业类库的时候文档是极度缺乏的。

在当先的Java环境中提供了两个工具来补偿这些,javap来打印类标识,javadoc为嵌入式程序提供HTML文档。

4.用Package来分解Java命名空间

在大的C++工程中经常遇到的一个问题是命名空间–怎样保证工程的一些程序员不会创建和另一些程序员一样名字的类?更糟糕的是,供应商可能会提 供一个包含和你的类一样名字的类的库。有许多方法可以解决这一问题,但是很可能在问题发现之前工程已经启动,改正错误是需要付出许多痛苦的。

Java通过”Package”这个概念解决了这个问题,Package有效地通过通过集合类划分了命名空间。在不同包内的两个同名的类仍然是不同的。关键问题就变成了类是否放置到相应的包中。

记住,Java并没有解决命名冲突的问题。扩展一个基类而引起了派生类的冲突。比如说,如果你最喜欢的供应商提供了一些类,然后你把它们用做基类 并且派生有一个foo方法的类,当供应商提供一个新版本的类的时候就可能出现,如果供应商业也在新类中提供了一个foo的方法。

5.异常是Java的重要特性

在C++中,异常和异常处理是十分深奥的事情;许多C++程序员从没有处理过它们甚至不知道它们是何物。异常是在正常的过程中出现的未预料的错 误,因此,它们不会从方法中返回,或者作为参数传入;但是,它们不能被忽略!这里的一个例子是计算一个书的方根的方法。正常的接口形式是将一个正数作为参 数传入方法,然后方法会返回一个正实数作为结果,方法可以检验这些并且在异常产生的时候抛出异常。在大多数系统中,程序员并不是必须这样做,这样,一个没 有考虑到的异常可以使程序不正常的退出。

在Java中,异常已经成为语言中非常成熟的部分。方法的说明中就包含了异常的信息,程序处理器也强制检验如果你使用了一个能够产生异常的方法, 你就必须检查异常是否发生。几乎所有的Java程序员都会遇到异常的情况,因为许多非常有用的库中的类都会抛出异常。处理异常并不难,但是在一些时候是需 要注意的。一个方法的文档会指明方法抛出的异常的类型。如果你忘了,不要紧,编译器会提醒你的。

6.字符串不再是字符数组

Java中包括了一个字符串的对象,并且是个常量。字符串不像字符数组一样,虽然可以简单的从一个字符数组构造一个字符串。你应该尽可能的用字符串代替字符数组,因为他们不会因为误操作而被覆盖。

7.Java限制了常量对象和方法

在C++中,你可以正式的声明一个函数参数或者函数返回值为const类型,这样可以有效的防止对参数或者返回值的不正当修改。另外,你可以声明一个成员函数为const,表明它不可以修改任何他操作的对象。

Java支持常量操作符,只读变量,这些通过final关键字实现。但是Java没有支持强制的使一个可写变量在函数传递、返回的过程中变为只读。或者定义一个不操作修改对象的常量方法。

在Java中,这个省略带来的影响和在C++中相比就非常小了,这很大程度上因为字符串变量和字符数组的不同,但是这也带来一个引起错误的隐患。特别地,没有办法检验一个方法是否可以改动对象。

8.Java没有指针

理解指针的概念是一个C或C++程序员最难应付的问题。指针也是错误产生的一大根源。Java中没有指针,对象的句柄直接作为参数传递,而不是传 递指针。另外,你必须通过索引使用数组。这些都不是什么大问题。然而,没有指针是在写含有函数指针或者成员函数指针的系统的时候引起很大麻烦。这个问题在 处理回调函数的时候更加显著。

9.Java没有参数化类型

参数化类型提供了用一段程序处理许多相似程序的方法。一个例子就是开平方根的方法,它可以对int或者float操作。在C++中,这一特性是由模板提供的。

Java中不包含C++中的模板的等价物。如果你经常使用模板来简化程序,比如说构造许多使用相似参数类型的函数,这简直就是灾难。这意味着更多使用复制、粘贴的过程来手动的完成。然而,如果你使用模板来生成类的话,没有简单的方法。

10.Java使用垃圾回收

在垃圾回收的语言中,运行时环境一直监测哪些内存不被使用。当一块内存不用的时候,系统自动的回收内存。比如说,一个对象在一个方法中生成,但是 没有被调用着返回或者没有储存为全局变量,不能在方法外部使用。系统自己会知道哪些变量是你用不到的,哪些是可以用到的。因此,你不必再为破坏对象回收内 存而担心。在C++中,很多的调试时间都被使用到检查内存漏洞中。Java的这种方法很大程度上降低了这种错误的可能。但是他依然不能处理逻辑混乱的程 序,他们不能够被回收。许多C++的类中的析构函数是用来释放对象引用的内存的。Java使垃圾回收的事实说明在Java中不是必需写析构函数了。但是并 不意味着你可以忘记为你的类写析构函数。比如,一个对象打开了网络连接就必须被恰当的清理来关闭这个连接。在Java中,析构函数被称 作”finalization”方法。

11.Java不支持多重继承

在任何一个复杂的面向对象的系统中,实现一个有更多方法的新类是十分经常遇到的事情。比如说,一个Manager类,需要被作为一个连表的表头, 但是一个Manager又必须是一个Employee。有许多方法来处理这样的问题。一个方法是允许从多个类继承。在这个例子中,Manager需要从 Linked List和Employee继承。

Java没有多重继承。但是你可以声明接口–来描述实现一些功能的编程接口。一个类可以由多个接口实现,包括他唯一的功能。不同的类可以由同样的接口实现。方法的参数既可以声明为类,也可以声明为接口。如果是接口的话,实现接口的类就可以作为参数传入方法。

接口的概念要比多继承容易理解一些,但是他有一定的局限性。特别地,你必须在类中实现接口的时候编码去重新实现类的功能。

12.Java支持多线程

多线程可以使你写出在同一时刻完成多种任务的程序。比如说,你可以在完成读取一个大文件之间允许用户对已经读取的部分进行编辑。你需要把程序分为多线程来执行。为安全起见。你的程序要被精心的设计,因为可能不止一个线程需要对数据进行访问、修改。

Java开始就支持多线程。类和接口用来分解一个程序成为不同的线程。语言简单的对重要的数据作同步或者锁定处理。

13.Java以一些预定义的类为基础

默认的Java环境中包括一些从Java基础类实现而来的一些包。这些允许你很快的写出一些有用的程序,这些包如下:

java.awt:当今许多应用程序都非常依赖GUI,java提供了一个Abstract Window Toolkid,这可以让你在不考虑运行平台的前提下处理GUI对象。

java.applet:applet的主要目的是提供浏览有关的内容。它本身是awt组件的字类并且支持其他一些特性,比如声音、渲染等。

java.io:java.io提供了对流、文件、管道的读写操作。

java.lang:提供了java的基础类Objcet,Integar,Float……;

java.net:提供对网络编程的支持。包括处理socket,URL,Internet寻址等。

java.util:为数据结构提供的通用实用工具集

Comments (323)

C/C++中near和far的区别 内存 指针

1顶一下关键字near和far受目标计算机体系结构的影响。目前编程中使用不多。 near关键字创建一个指向可寻址内存低端部分的目标指针。这些指针占用内存的单一字节,并且他们能够指向的内存单元被限制到256个位置,通常是在 0×0000~0x00ff范围中。 int near * ptr; far关键字创建一个能够指向内存中任何数据的指针: char far * ptr; near (近)指针:16位段内偏移地址 far(远)指针:16位段...[阅读全文]

1
顶一下

关键字near和far受目标计算机体系结构的影响。目前编程中使用不多。 near关键字创建一个指向可寻址内存低端部分的目标指针。这些指针占用内存的单一字节,并且他们能够指向的内存单元被限制到256个位置,通常是在 0×0000~0x00ff范围中。 int near * ptr; far关键字创建一个能够指向内存中任何数据的指针: char far * ptr; near (近)指针:16位段内偏移地址 far(远)指针:16位段地址+16位段内偏移地址 huge(巨)指针:32位规格化的具有唯一性的内存地址 C语言的存贮属性由六种编译模式决定(参见TC集成环境菜单中的option->compiler->model选项),默认的编译模式为 small, 在该编译模式下,指针的默认属性为near。 补充:near指针是16位指针,依赖一个段地址寄存器,指针变量就是位移量,利用 段地址寄存器+指针 来寻址,所以有64K之限制。 far 指针是32位指针,不但有16位的位移量,还有16位的段地址,但此指针有个缺陷,增量时只加到位移部分,一旦16位的位移量超过了FFFF就会回到这个 段地址的初始。 所以,又引入了huge指针,huge指针与far一样,其区别仅在于使用了标准化的方法来表示,这样所有的地址都有一个唯一的表示方法,从而避免了 far指针的问题。 空指针规定了一种指针状态,如果没有这个空指针,就如数字没有了0

Comments (281)

C语言头文件避免重复包含

0顶一下 假定有以下几个头文件及其包含关系为: File1.h,file2.h,file3.h,file4.h,file5.h,main.cpp 那么:file3.h包含file1.h,file2.h,file4.h包含 file1.h,file2.h,file5.h包含file3.h,file4.h。如许就会导致在file5中对file1和file2的反复包含, 编译时就会报错。 解决方法: 1:应用#ifndef #define #endif 即每个文件在定义时都写成以下情势(以file1.h为例): #ifndefH...[阅读全文]

0
顶一下

假定有以下几个头文件及其包含关系为:

File1.h,file2.h,file3.h,file4.h,file5.h,main.cpp

那么:file3.h包含file1.h,file2.h,file4.h包含 file1.h,file2.h,file5.h包含file3.h,file4.h。如许就会导致在file5中对file1和file2的反复包含, 编译时就会报错。

解决方法:

1:应用#ifndef

#define

#endif

即每个文件在定义时都写成以下情势(以file1.h为例):

#ifndefH_FILE1

#defineH_FILE1

#include<stdio.h>

#include<math.h>

…..

#endif

File3.h:#ifndefH_FILE3

#defineH_FILE3

#include<stdio.h>

#include<math.h>

#inlcude”file1.h”

#include”file2.h”

…..

#endif

方法二:在每个文件的头部定义:#pragmaonce(用于解释本文件中的内容只应用一次)

例:fiel1.h:

#pragmaonce

#include<stdio.h>

#include<math.h>

…..

File3.h:

#pragmaonce

#include<stdio.h>

#include<math.h>

#include”file1.h”

…..

Comments (213)

C++中获取高精度时间差

0顶一下 解决一个问题通常有多种方法, 我们总想找到最高效的,所以需要对比不同算法执行所用的时间。可惜的是,C++中 提供的方法一般只能精确到毫秒级。 提供一种更加精确的方法。编写一个函数,可以在C++中这样写: __declspec (naked) unsigned __int64 GetCpuCycle( void ) { _asm { rdtsc ret } } RDTSC的返回值存放在EDX EAX中, EDX为高32位,EAX为低32位。这里的 RDTSC 指令...[阅读全文]

0
顶一下

解决一个问题通常有多种方法, 我们总想找到最高效的,所以需要对比不同算法执行所用的时间。可惜的是,C++中 提供的方法一般只能精确到毫秒级。

提供一种更加精确的方法。编写一个函数,可以在C++中这样写:

__declspec (naked) unsigned __int64 GetCpuCycle( void )

{

_asm

{

rdtsc

ret

}

}

RDTSC的返回值存放在EDX EAX中, EDX为高32位,EAX为低32位。这里的 RDTSC 指令( Read Time Stamp Counter ), 获得CPU的高精度时间戳。

这样以来我们就可以在随处获得当前的CPU自上电以来的时间周期数了:

unsigned __int64 iCpuCycle = GetCpuCycle();

根据这个数字我们可以计算出上电以来所经历的时间( 秒s ):

second = iCpuCycle / CPU主频率( HZ );

1GHZ = 1,000 MHZ = 1,000,000 KHZ = 1,000,000,000 HZ;

获取两次作差就可以得到运行的时间了。其实没必要换算成时间,关注差值就行了。

PS:

可以放心一个unsigned __int64 不会溢出 – - 可以计算一下你的CPU能保存多少年的时间。。

根据这一方法有几个好处: 一是精度高,二是函数调用开销最小,三是平台限制小,四是具有和CPU主频相对应的直接关系。。。 但是由于精度高,得到的数字浮动比较大。。

Comments (261)

C++中内存泄漏的检测

0顶一下首先我们需要知道程序有没有内存泄露,然后定位到底是哪行代码出现内存泄露了,这样才能将其修复。 最简单的方法当然是借助于专业的检测工具,比较有名如BoundsCheck,功能非常强大,相信做C++开 发的人都离不开它。此外就是不使用任何工具,而是自己来实现对内存泄露的监控,分如下两种情况: 一. 在 MFC 中检测内存泄漏 假如是用MFC的程序的话,很简单。默认的就有内存泄露检测的功能。 ...[阅读全文]

0
顶一下

首先我们需要知道程序有没有内存泄露,然后定位到底是哪行代码出现内存泄露了,这样才能将其修复。

最简单的方法当然是借助于专业的检测工具,比较有名如BoundsCheck,功能非常强大,相信做C++开 发的人都离不开它。此外就是不使用任何工具,而是自己来实现对内存泄露的监控,分如下两种情况:

一. 在 MFC 中检测内存泄漏

假如是用MFC的程序的话,很简单。默认的就有内存泄露检测的功能。

我们用VS2005生成了一个MFC的对话框的程序,发现他可以自动的检测内存泄露.不用我们做任何特殊的操作. 仔细观察,发现在每个CPP文件中,都有下面的代码:

#ifdef _DEBUG

#define new DEBUG_NEW

#endif

DEBUG_NEW 这个宏定义在afx.h文件中,就是它帮助我们定位内存泄漏。

在含有以上代码的cpp文件中分配内存后假如没有删除,那么停止程序的时候,VisualStudio的Output窗口就会显示如下的信息 了:

Detected memory leaks!

Dumping objects ->

d:\code\mfctest\mfctest.cpp(80) : {157} normal block at 0x003AF170, 4 bytes long.

Data: < > 00 00 00 00

Object dump complete.

在Output窗口双击粗体字那一行,那么IDE就会打开该文件,定位到该行,很容易看出是哪出现了内存泄露。

二.检测纯C++的程序内存泄露

我试了下用VisualStudio建立的Win32 Console Application和Win32 Project项目,结果都不能检测出内存泄露。

下面一步一步来把程序的内存泄露检测的机制建立起来。

首先,我们需要知道C运行库的Debug版本提供了许多检测功能,使得我们更容易的Debug程序。在MSDN中有专门的章节讲这个,叫做 Debug Routines,建议大家先看看里面的内容吧。

我们会用到里面很重要的几个函数。其中最重要的是 _CrtDumpMemoryLeaks;自己看MSDN里的帮助吧。使用这个函数,需要包含头文件crtdbg.h

该函数只在Debug版本才有用,当在调试器下运行程序时,_CrtDumpMemoryLeaks 将在“Output(输出)”窗口中显示内存泄漏信息.写段代码试验一下吧,如下:

检测内存泄露版本一:

#include “stdafx.h”

#include <crtdbg.h>

int _tmain(int argc, _TCHAR* argv[])

{

int* p = new int;

_CrtDumpMemoryLeaks;

return 0;

}

运行后,在Output(输出)窗口,显示了如下的信息:

Detected memory leaks!

Dumping objects ->

{112} normal block at 0x003AA770, 4 bytes long.

Data: <    > 00 00 00 00

Object dump complete.

但是这个只是告诉我们程序有内存泄露,到底在哪泄露了一眼看不出来啊。

看我们的检测内存泄露版本二:

#include “stdafx.h”

#ifdef _DEBUG

#define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)

#else

#define DEBUG_CLIENTBLOCK

#endif

#define _CRTDBG_MAP_ALLOC

#include <crtdbg.h>

#ifdef _DEBUG

#define new DEBUG_CLIENTBLOCK

#endif

int _tmain(int argc, _TCHAR* argv[])

{

int* p = new int;

_CrtDumpMemoryLeaks;

return 0;

}

该程序定义了几个宏,通过宏将Debug版本下的new给替换了,新的new记录下了调用new时的文件名和代码行.运行后,可以看到如下的结 果:

Detected memory leaks!

Dumping objects ->

d:\code\consoletest\consoletest.cpp(21) : {112} client block at 0x003A38B0, subtype 0, 4 bytes long.

Data: <    > 00 00 00 00

Object dump complete.

呵呵,已经和MFC程序的效果一样了,但是等一等。看下如下的代码吧:

int _tmain(int argc, _TCHAR* argv[])

{

int* p = new int;

_CrtDumpMemoryLeaks;

delete p;

return 0;

}

运行后可以发现我们删除了指针,但是它仍然报内存泄露。所以可以想象,每调用一次new,程序内部都会将该调用记录下来,类似于有个数组记录, 假如delete了,那么就将其从数组中删除,而_CrtDumpMemoryLeaks就是把这个数组当前的状态打印出来。

所以除了在必要的时候Dump出内存信息外,最重要的就是在程序退出的时候需要掉用一次_CrtDumpMemoryLeaks;

假如程序有不止一个出口,那么我们就需要在多个地方都调用该函数。

更进一步,假如程序在类的析构函数里删除指针,怎么办?例如:

#include “stdafx.h”

#ifdef _DEBUG

#define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)

#else

#define DEBUG_CLIENTBLOCK

#endif

#define _CRTDBG_MAP_ALLOC

#include <crtdbg.h>

#ifdef _DEBUG

#define new DEBUG_CLIENTBLOCK

#endif

class Test

{

public:

Test      {   _p = new int;     }

~Test     {   delete _p;          }

int* _p;

};

int _tmain(int argc, _TCHAR* argv[])

{

int* p = new int;

delete p;

Test t;

_CrtDumpMemoryLeaks;

return 0;

}

可以看到析构函数在程序退出的时候才调用,明明没有内存泄露,但是这样的写法还是报了。

如何改进呢,看检测内存泄露版本三:

#include “stdafx.h”

#ifdef _DEBUG

#define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)

#else

#define DEBUG_CLIENTBLOCK

#endif

#define _CRTDBG_MAP_ALLOC

#include <crtdbg.h>

#ifdef _DEBUG

#define new DEBUG_CLIENTBLOCK

#endif

class Test

{

public:

Test      {   _p = new int;     }

~Test     {   delete _p;          }

int* _p;

};

int _tmain(int argc, _TCHAR* argv[])

{

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

int* p = new int;

delete p;

Test t;

return 0;

}

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );该语句在程序退出时自动调用 _CrtDumpMemoryLeaks。必须同时设置 _CRTDBG_ALLOC_MEM_DF 和 _CRTDBG_LEAK_CHECK_DF.

这样,该版本已经达到了MFC一样的效果了,但是我觉得光这样还不够,因为我们只是在Output窗口中输出信息,对开发人员的提醒还不明显, 经常会被遗漏,而且很多人就算发现了内存泄露,但是不好修复,不会严重影响到程序外在表现,都不会修复。怎么样能让开发人员主动的修复内存泄露的问题呢? 记得曾经和人配合写程序,我的函数参数有要求,不能为空,但是别人老是传空值,没办法了,只好在函数开始验证函数参数,给他assert住,这样程序运行 时老是不停的弹出assert,调试程序那个烦压,最后其他程序员烦了,就把这个问题给改好了,输入参数就正确了。所以我觉得咱要让程序员主动去做一件 事,首先要让他觉得做这个事是能减轻自己负担,让自己工作轻松的。呵呵,那咱们也这样,当程序退出时,检测到内存泄露就让程序提示出来。

看检测内存泄露版本四:

#include “stdafx.h”

#include <assert.h>

#ifdef _DEBUG

#define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)

#else

#define DEBUG_CLIENTBLOCK

#endif

#define _CRTDBG_MAP_ALLOC

#include <crtdbg.h>

#ifdef _DEBUG

#define new DEBUG_CLIENTBLOCK

#endif

void Exit

{

int i = _CrtDumpMemoryLeaks;

assert( i == 0);

}

int _tmain(int argc, _TCHAR* argv[])

{

atexit(Exit);

int* p = new int;

return 0;

}

该版本会在程序退出时检查内存泄露,假如存在就会弹出提示对话框.

atexit(Exit);设置了在程序退出时执行Exit函数。Exit函数中,假如存在内存泄露,_CrtDumpMemoryLeaks 会返回非0值,就会被assert住了。

到这个版本已经达到可以使用的程度了。但是我们还可以做些改进,因为真要准确的检测到代码中所有的内存泄露,需要把代码中的#define…… 拷贝到所有使用new的文件中。不可能每个文件都拷贝这么多代码,所以我们可以将他提取出来,放在一个文件中,比如我是放在 KDetectMemoryLeak.h中,该文件内容如下:

#pragma once

#ifdef _DEBUG

#define DEBUG_CLIENTBLOCK   new( _CLIENT_BLOCK, __FILE__, __LINE__)

#else

#define DEBUG_CLIENTBLOCK

#endif

#define _CRTDBG_MAP_ALLOC

#include <stdlib.h>

#include <crtdbg.h>

#ifdef _DEBUG

#define new DEBUG_CLIENTBLOCK

#endif

然后将KDetectMemoryLeak.h包含在项目的通用文件中,例如用VS建的项目就将其包含在stdafx.h中。或者我自己建的一 个Common.h文件中,该文件包含一些通用的,基本所有文件都会用到的代码。

Comments (277)