??xml version="1.0" encoding="utf-8" standalone="yes"?> 一、面向过E设计中的static 1、静(rn)态全局变量 在全局变量前,加上关键字staticQ该变量p定义成ؓ(f)一个静(rn)态全局变量。我们先举一个静(rn)态全局变量的例子,如下Q? 一般程序的由new产生的动态数据存攑֜堆区Q函数内部的自动变量存放在栈区。自动变量一般会(x)随着函数的退?gu)释攄_(d)?rn)态数据(即是函数内部的?rn)态局部变量)(j)也存攑֜全局数据区。全局数据区的数据q不?x)因为函数的退?gu)释攄间。细?j)的读者可能会(x)发现QExample 1中的代码中将 (zhn)可以将上述CZ代码改ؓ(f)如下Q?br />
2、静(rn)态局部变?/p>
在局部变量前Q加上关键字staticQ该变量p定义成ؓ(f)一个静(rn)态局部变量? 我们先D一个静(rn)态局部变量的例子Q如下:(x) ?rn)态局部变量有以下特点Q?/p>
3、静(rn)态函?/p>
在函数的q回cd前加上static关键?函数卌定义为静(rn)态函数。静(rn)态函C普通函C同,它只能在声明它的文g当中可见Q不能被其它文g使用?/p>
?rn)态函数的例子Q? 二、面向对象的static关键字(cM的static关键字)(j) 1、静(rn)态数据成?/p>
在类内数据成员的声明前加上关键字staticQ该数据成员是cd的静(rn)态数据成员。先举一个静(rn)态数据成员的例子? 与静(rn)态数据成员(sh)P我们也可以创Z个静(rn)态成员函敎ͼ它ؓ(f)cȝ全部服务而不是ؓ(f)某一个类的具体对象服务。静(rn)态成员函C?rn)态数据成员(sh)P都是cȝ内部实现Q属于类定义的一部分。普通的成员函数一般都隐含?jin)一个this指针Qthis指针指向cȝ对象本nQ因为普通成员函数L具体的属于某个类的具体对象的。通常情况下,this是缺省的。如函数fn()实际上是this->fn()。但是与普通函数相比,?rn)态成员函数由于不是与M的对象相联系Q因此它不具有this指针。从q个意义上讲Q它无法讉K属于cd象的非静(rn)态数据成员,也无法访问非?rn)态成员函敎ͼ它只能调用其余的?rn)态成员函数。下面D个静(rn)态成员函数的例子?
char * s = const_cast<char *>(str);
s[0] = 'A';
cout<<s<<endl; //输出Abc
但是q样׃行了(jin)Q?
const char *str = "abc";
char * s = const_cast<char *>(str);
s[0] = 'A';
cout<<s<<endl; //~译能通过Q但是执行就报错?br />
: const char str[] = "abc"; abc存储在堆栈中Q?/font>
: const char *str = "abc"; abc存储在静(rn)态存储区Q?/font>
准确的说Q上面两?#8220;abc"都是存储在静(rn)态存储区Q即帔R区。常量区是可M可写的。所以Q何试囑֯帔R行写的操作都是非法的Q当然了(jin)Q这也不是一定不可写的,你可以采取某U渠道改变常量区的内存属性,比如改变pe相关节的属性就可以对常量区q行dQ当然了(jin)Q这个目前可以忽略。。?
那么Z么str[] = "abc"; 可以写呢Q答案就在str[] = "abc";?x)有一个额外的拯q程Q即把常量区?"abc"拯到栈内存去,所以就可以写了(jin)?
//Example 1
#include <iostream.h>
void fn();
static int n; //定义?rn)态全局变量
void main()
{
n=20;
cout<<n<<endl;
fn();
}
void fn()
{
n++;
cout<<n<<endl;
}
?rn)态全局变量有以下特点:(x)
?rn)态变量都在全局数据区分配内存,包括后面要提到的静(rn)态局部变量。对于一个完整的E序Q在内存?sh)的分布情况如下图?x)
代码?/td>
全局数据?/td>
堆区
栈区
static int n; //定义?rn)态全局变量
改ؓ(f)
int n; //定义全局变量
E序照样正常q行?br />
的确Q定义全局变量可以实现变量在文g中的׃nQ但定义?rn)态全局变量q有以下好处Q?
//Example 2
//File1
#include <iostream.h>
void fn();
static int n; //定义?rn)态全局变量
void main()
{
n=20;
cout<<n<<endl;
fn();
}
//File2
#include <iostream.h>
extern int n;
void fn()
{
n++;
cout<<n<<endl;
}
~译q运行Example 2Q?zhn)׃?x)发现上述代码可以分别通过~译Q但q行时出现错误。试着?
static int n; //定义?rn)态全局变量
改ؓ(f)
int n; //定义全局变量
再次~译q行E序Q细?j)体会(x)全局变量和静(rn)态全局变量的区别?
//Example 3
#include <iostream.h>
void fn();
void main()
{
fn();
fn();
fn();
}
void fn()
{
static n=10;
cout<<n<<endl;
n++;
}
通常Q在函数体内定义?jin)一个变量,每当E序q行到该语句旉?x)给该局部变量分配栈内存。但随着E序退出函CQ系l就?x)收回栈内存Q局部变量也相应失效?br />
但有时候我们需要在两次调用之间对变量的D行保存。通常的想法是定义一个全局变量来实现。但q样一来,变量已经不再属于函数本n?jin),不再仅受函数的控Ӟl程序的l护带来不便?br />
?rn)态局部变量正好可以解册个问题。静(rn)态局部变量保存在全局数据区,而不是保存在栈中Q每ơ的g持到下一ơ调用,直到下次赋新倹{?
//Example 4
#include <iostream.h>
static void fn();//声明?rn)态函?
void main()
{
fn();
}
void fn()//定义?rn)态函?
{
int n=10;
cout<<n<<endl;
}
定义?rn)态函数的好处Q?
//Example 5
#include <iostream.h>
class Myclass
{
public:
Myclass(int a,int b,int c);
void GetSum();
private:
int a,b,c;
static int Sum;//声明?rn)态数据成?
};
int Myclass::Sum=0;//定义q初始化?rn)态数据成?
Myclass::Myclass(int a,int b,int c)
{
this->a=a;
this->b=b;
this->c=c;
Sum+=a+b+c;
}
void Myclass::GetSum()
{
cout<<"Sum="<<Sum<<endl;
}
void main()
{
Myclass M(1,2,3);
M.GetSum();
Myclass N(4,5,6);
N.GetSum();
M.GetSum();
}
可以看出Q静(rn)态数据成员有以下特点Q?
2、静(rn)态成员函?
//Example 6
#include <iostream.h>
class Myclass
{
public:
Myclass(int a,int b,int c);
static void GetSum();/声明?rn)态成员函?
private:
int a,b,c;
static int Sum;//声明?rn)态数据成?
};
int Myclass::Sum=0;//定义q初始化?rn)态数据成?
Myclass::Myclass(int a,int b,int c)
{
this->a=a;
this->b=b;
this->c=c;
Sum+=a+b+c; //非静(rn)态成员函数可以访问静(rn)态数据成?
}
void Myclass::GetSum() //?rn)态成员函数的实现
{
// cout<<a<<endl; //错误代码Qa是非?rn)态数据成?
cout<<"Sum="<<Sum<<endl;
}
void main()
{
Myclass M(1,2,3);
M.GetSum();
Myclass N(4,5,6);
N.GetSum();
Myclass::GetSum();
}
Q类名>::Q静(rn)态成员函数名Q(Q参数表Q)(j)
调用cȝ?rn)态成员函数?
static关键字有两种意?你看上下文来判断
1,表示变量是静(rn)态存储变?
表示变量存放在静(rn)态存储区.
2,表示该变量是内部q接
(q种情况是指该变量不在Q何{}之内,p全局变量那样,q时候加上static)
,也就是说在其它的.cpp文g?该变量是不可见的(你不能用).
当static加在函数前面的时?
表示该函数是内部q接,之在本文件中有效,别的文g中不能应用该函数.
不加static的函数默认ؓ(f)是全局?
也就是说在其他的.cpp中只要申明一下这个函?可以用它.
1、static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?
{:(x)全局变量(外部变量)的说明之前再冠以static 构成了(jin)?rn)态的全局变量。全局变量本n是?rn)态存储方式, ?rn)态全局变量当然也是?rn)态存储方式?q两者在存储方式上ƈ无不同。这两者的区别虽在于非?rn)态全局变量的作用域是整个源E序Q当一个源E序由多个源文gl成Ӟ非静(rn)态的全局变量在各个源文g中都是有效的?而静(rn)态全局变量则限制了(jin)其作用域Q?卛_在定义该变量的源文g内有效, 在同一源程序的其它源文件中不能使用它。由于静(rn)态全局变量的作用域局限于一个源文g内,只能源文件内的函数公用, 因此可以避免在其它源文g中引起错误?br />
从以上分析可以看出, 把局部变量改变(sh)ؓ(f)?rn)态变量后是改变(sh)(jin)它的存储方式x变(sh)(jin)它的生存期。把全局变量改变?sh)?rn)态变量后是改变(sh)(jin)它的作用域, 限制?jin)它的用范围?br />
static函数与普通函C用域不同。static函数仅在本文件中使用。只在当前源文g中用的函数应该说明为内部函?static)Q内部函数应该在当前源文件中说明和定义。对于可在当前源文g以外使用的函敎ͼ应该在一个头文g中说明,要用这些函数的源文件要包含q个头文?br />
static全局变量与普通的全局变量有什么区别:(x)static全局变量只初使化一ơ,防止在其他文件单元中被引?
static局部变量和普通局部变量有什么区别:(x)static局部变量只被初始化一ơ,下一ơ依据上一ơ结果|
static函数与普通函数有什么区别:(x)static函数在内存(sh)只有一份,普通函数在每个被调用中l持一份拷?/p>
2、如何引用一个已l定义过的全局变量Q?/strong>
{:(x)extern
可以用引用头文g的方式,也可以用extern关键字,如果用引用头文g方式来引用某个在头文件中声明的全局变理Q假定你那个变写错?jin),那么在编译期??x)报错,如果你用extern方式引用Ӟ假定你犯?jin)同L(fng)错误Q那么在~译期间不会(x)报错Q而在q接期间报错?/p>
3、全局变量可不可以定义在可被多?C文g包含的头文g中?Z么?
{:(x)可以Q在不同的C文g中以static形式来声明同名全局变量?br />
可以在不同的C文g中声明同名的全局变量Q前提是其中只能有一个C文g中对此变量赋初|此时q接不会(x)出错?/p>
函数TheFunction()的返回D赋值给对SimpleCatcȝ一个引?然后使用q个引用来获得对象的成员变量age的?br /> Z(jin)证明函数TheFunction()在自由存储区中创建的对象被赋l了(jin)函数main()声明的引用,对rCat采取取址q算Q很明显Q它昄?jin)其引用的对象的地址Qƈ且和自由存储Z的对象的地址相匹配?br /> q一切看上去都很正确Q但是如何释放被占用的内存,对于引用是不能用deleteq算W的Q一个笔记聪明的Ҏ(gu)是声明另一个指针,q用由rCat得到的地址对它q行初始化。这样做实可以释放内存Q避免了(jin)内存的泄漏,但是存在一个小问题Q在delete pCat后,rCat引用又代表什么呢Q?引用必须是一个实际对象的别名Q如果引用一个空对象的话Q现在就是这U情况)(j)那么E序是无效的。?/p>
注意Q不能过分去一个用对I对象的引用的程序,也行能通过~译Q但是即使程序通过?jin)编译,它仍然是病态的Q而且执行l果也是不可预料?/p>
关于q个问题Q存在这3U解x?br />
W?U是在第49行声明一个SimpleCatcd象,然后从函数TheFunction()中采取g递的方式q回它的局部对象,q时不能在自由存储区来分配对象空_(d)不能使用newQ?br />
W?U是在函数TheFunction()中声明一个位于自由存储区中的SimpleCatcȝ对象Q但是让函数q回一个指向内存区域的指针Q然后完成对对象的操作,调用函数可以删除q个指针
W?U可行的Ҏ(gu)Q也是正的Q是在调用函C声明对象Q然后通过引用的方式把它传递给函数TheFunction()
Person.java
class Person
{
public String name = "not know";//4
public int age = -1 ;//5
public Person()
{
}
public Person(String name , int age)//3
{
this.name = name ;//6
this.age = age;//7
}
public void showInfo()
{
System.out.println("Name is :"+name+" Age is :"+age);
}
}
class Student extends Person
{
public String school = "not konwn";//8
public Student()
{
super();
}
public Student (String name ,int age, String school) //1
{
super(name,age); //2
this.school = school;//9
}
public void showInfo()
{
super.showInfo();
System.out.println("School is :"+school);
}
}
AllTest.java
class AllTest
{
public static void main(String[]args)
{
Student stA = new Student("wanghao",20,"qinghuadaxue"); //0
}
}
其语句的执行序是怎样的呢Q?/span>
java中,在?/span>new操作W创Z个类的实例对象的时候,开始分配空间ƈ成员变量初始化?/span>默认?/span>数?/span>Q注意这里ƈ不是指将变量初始化ؓ(f)在变量定义处的初始|?strong>是给整Ş赋?/span>0Q给字符串赋?/span>null q一点于C++不同Q(student.name = null , student.age = 0 Q?/span>
然后在进入类的构造函数?/span>
在构造函数里面,首先要检查是否有this或?/span>super调用Q?/span>this调用是完成本cLw的构造函C间的调用Q?/span>super调用是完成对父类的调用?strong>二者只能出C个,q且只能作ؓ(f)构造函数的W一句出现?/span>在调?/span>this?/span>super的时候实现程序的跌{Q{而执行被调用?/span>this构造函数或?/span>super构造函数?/span>
?/span>this?/span>super执行完毕Q?strong>E序转而执行在cd义的时候进行的变量初始化工?/span>?/span>
q个执行完毕Q才是构造函C剩下的代码的执行?/span>
执行序已经用绿色标出?/span>
Order of initialization
Within a class, the order of initialization is determined by the order that the variables are defined within the class. The variable definitions may be scattered throughout and in between method definitions, but the variables are initialized before any methods can be called—even the constructor. For example: Feedback
//: c04:OrderOfInitialization.java
// Demonstrates initialization order.
import com.bruceeckel.simpletest.*;
// When the constructor is called to create a
// Tag object, you'll see a message:
class Tag {
Tag(int marker) {
System.out.println("Tag(" + marker + ")");
}
}
class Card {
Tag t1 = new Tag(1); // Before constructor
Card() {
// Indicate we're in the constructor:
System.out.println("Card()");
t3 = new Tag(33); // Reinitialize t3
}
Tag t2 = new Tag(2); // After constructor
void f() {
System.out.println("f()");
}
Tag t3 = new Tag(3); // At end
}
public class OrderOfInitialization {
static Test monitor = new Test();
public static void main(String[] args) {
Card t = new Card();
t.f(); // Shows that construction is done
monitor.expect(new String[] {
"Tag(1)",
"Tag(2)",
"Tag(3)",
"Card()",
"Tag(33)",
"f()"
});
}
} ///:~
//: c04:StaticInitialization.java
// Specifying initial values in a class definition.
class Bowl {
Bowl(int marker) {
System.out.println("Bowl(" + marker + ")");
}
void f(int marker) {
System.out.println("f(" + marker + ")");
}
}
class Table {
static Bowl b1 = new Bowl(1);
Table() {
System.out.println("Table()");
b2.f(1);
}
void f2(int marker) {
System.out.println("f2(" + marker + ")");
}
static Bowl b2 = new Bowl(2);
}
class Cupboard {
Bowl b3 = new Bowl(3);
static Bowl b4 = new Bowl(4);
Cupboard() {
System.out.println("Cupboard()");
b4.f(2);
}
void f3(int marker) {
System.out.println("f3(" + marker + ")");
}
static Bowl b5 = new Bowl(5);
}
public class StaticInitialization {
static Test monitor = new Test();
public static void main(String[] args) {
System.out.println("Creating new Cupboard() in main");
new Cupboard();
System.out.println("Creating new Cupboard() in main");
new Cupboard();
t2.f2(1);
t3.f3(1);
monitor.expect(new String[] {
"Bowl(1)",
"Bowl(2)",
"Table()",
"f(1)",
"Bowl(4)",
"Bowl(5)",
"Bowl(3)",
"Cupboard()",
"f(2)",
"Creating new Cupboard() in main",
"Bowl(3)",
"Cupboard()",
"f(2)",
"Creating new Cupboard() in main",
"Bowl(3)",
"Cupboard()",
"f(2)",
"f2(1)",
"f3(1)"
});
}
static Table t2 = new Table();
static Cupboard t3 = new Cupboard();
} ///:~
l承情况下的初始?br />
?jin)解一下包括承在内的初始化的q程是非常有益的,q样p有个?br />
体的?jin)解。看看下面这D代码:(x)
//: c06:Beetle.java
// The full process of initialization.
class Insect {
protected static Test monitor = new Test();
private int i = 9;
protected int j;
Insect() {
System.out.println("i = " + i + ", j = " + j);
j = 39;
}
private static int x1 = print("static Insect.x1 initialized");
static int print(String s) {
System.out.println(s);
return 47;
}
}
public class Beetle extends Insect {
private int k = print("Beetle.k initialized");
public Beetle() {
System.out.println("k = " + k);
System.out.println("j = " + j);
}
private static int x2 = print("static Beetle.x2 initialized");
public static void main(String[] args) {
System.out.println("Beetle constructor");
Beetle b = new Beetle();
monitor.expect(new String[] {
"static Insect.x1 initialized",
"static Beetle.x2 initialized",
"Beetle constructor",
"i = 9, j = 0",
"Beetle.k initialized",
"k = 47",
"j = 39"
});
}
} ///:~
你寻扄~译?/span>Beetle cȝ代码(也就?/span>Beetle.class 文g)。在装蝲的过E中Q装载器注意到它有一个基c?/span>(也就?/span>extends 所要表C的意?/span>)Q于是它再装载基cR不你创不创徏基类对象Q这个过EM(x)发生?/span>(试试看,把创建对象的那句注释掉,看看?x)有什么结果?/span>)如果基类q有基类Q那么这W二个基cM?x)被装蝲Q以此类?span style="color: blue">?strong>下一步,它会(x)执行“根基c?/strong>(root bas e class)”(q里是Insect)?/span>static 初始化,然后是下一个派生类?/span>static 初始化,以此cL。这个顺序非帔R要,因ؓ(f)zcȝ“?rn)态初始化(卛_面讲?/span>static 初始?/span>)”有可能要依赖基类成员的正初始化?/span>
现在所有必要的c都已经装蝲l束Q可以创建对象了(jin)?/span>首先Q对象里的所有的primitive 都会(x)被设成它们的~省|?/span>reference 也会(x)被设?/span>null——这个过E是一瞬间完成的,对象的内存(sh)(x)被统一地设|成“两进制的?/span>(binary zero)”?strong>如果有对成员变量赋初|则对成员变量q行赋|然后调用基类的构造函数。调用是自动发生的,但是你可以?/span>super 来指定调用哪个构造函?/span>(也就Beetle( )构造函数所做的W一件事)。基cȝ构造过E以?qing)构造顺序,同派生类的相同。基cL造函数运行完毕之后,?x)按照各个变量的字面序q行初始化。最后会(x)执行构造函数的其余部分?/span>
没有问题Q它q回的是一个|而不是引用,所以是正确的。在item1 = item2 + item3 中发生了(jin)一ơ值拷?赋?Q也是item2+item3q回的局部变量拷贝给?jin)item1之后Q局部变量的作用域结?nbsp;
可以q回局部变量,但是不能q回局部变量的引用。理解区分值和引用q两个概忉|学习(fn)C++的一大关键,明白q两个概念之后,你就?x)理解?f)什么C++的类里面需要有拯构造函敎ͼ赋值操作符Q析构函C个元素了(jin)以及(qing)其它的一些稀里古怪的用法和忠告了(jin)
--
Sales_item operator+(const Sales_item& lhs,const Sales_item& rhs)
{
Sales_item ret(lhs) ;
ret+= rhs ;
return ret ;
}
在return ret 时ƈ不是单的q回q个局部变量,而是q回的是ret的一个副本temp,
temp是通过拯构造函数实现的 ?Sales_item temp(ret);也就是说在主函数中用的其实是个temp,而ret早在operator+调用完毕后就释放?span style="color: red">Q而tempq个 对象一直在dC贮存Q虽然显C是看不到的 Q?strong> ????
嗯嗯Q(f)时对象一直是个有争议的话题?span style="color: red">temp只是临时构造的Q在赋值完毕之后它?yu)析构?jin)Q不是一直在dC贮存的。这个(f)时对象的作用域是什么?Q?
在你提的q种情况下,其实~译器是可以优化掉这个副本的Q但是可惜C++标准只允怼?nbsp; “Sales_item item1=item2+item3” q种情况?也就是在拯构造这U情况下Q编译器都不再生副本,而赋D是不行的。也许在未来的C++标准由可能通过拓展语义来消除(f)时对象,毕竟临时对象成ؓ(f)影响C++效率的一个主要因素?
--
???
目前的C++标准不允许在赋值的时候优化掉函数q回的副本,也就是下面这两种情况是不一L(fng)
//q是赋值给item1Q目前的标准下是?x)生副?temp
Sales_item item1,item2,item3;
item1=item2+item3;
//q是拯构造item1Q编译器通常?x)优?span style="color: #ff0000">(q个优化区别下面所说的NRV优化)掉副本,也就是不产生副本 temp
Sales_item item2,item3;
Sales_item item1=item2+item3;
看一下代码中的TheFunctionTwo();
译QAqua prglab.com
注:(x)很多E序员包括本人在内都是先学会(x)的JavaQ然后才学的C++Q其实C++与Java有很多相似和互通之处,有相当的Java知识可以对应转化到C++概念Q从而帮助我们快速上手。这文章介l的内容对从Java向C++转变很有帮助Q所以翻译推荐给有同样需要的朋友。翻译中加入?jin)本人的理解Q不完全是全文照搬。有不明或异议,请参考原文,或留a讨论。还是那句话Q推荐多d版资料?/p>
学习(fn)怎样从一U编E语a转移到另一U编E语a是今日的E序员必面对的现实Q还好,C++和Java有很多共同的特点Q所以从Java转到C++容易许多。C++比Java复杂很多Q本文ƈ没打涵盖所有C++的功能。但是如果你能够熟练掌握本文中的所有知识点Q也_有效的用C++?jin)?/p>
q里我们只讲Java与C++的不同之处。像程控制(if, while, for)q些在C++与Java中完全一L(fng)内容q里׃讲了(jin)?/p>
本文是基于ANSI C++标准的,一些老的C++ ~译器可能不支持q里讲到的一些重要功能。要使用q些~译器,你需要更多学?fn)C++中从Cl承来的部分。那些都出?jin)本文的范畴Q也׃介绍?jin)?/p>
C++ 中的变量cd与Java很相伹{像Java一PC++ ?tt>int?doublecd。但是这些数字类型的取D围是依赖于机器的?/span>比如?6位系l上Q例如运行DOS 或Windows 3.x的PCZQ?tt>int是双字节(2-byte)的,取D围比Java?-byte?tt>int要小很多。在q些机器上,如果 int 不够用的话,你需要用长整型long?/p>
C++ ?short?unsignedcd来更有效的存储数字。(我认为所谓有效是指更高的I间利用率。)(j) 最好是量避免使用q些cd除非是空间利用的有效性对你的pȝ真的非常重要?/p>
在C++中布?yu)(dng)型?bool表示Q而不像在Java中用boolean?/span>
C++ 中字W串cd?string表示。它与Java中的 Stringcd非常怼Q但是,q是要逐一以下几点不同之处Q?/p>
1. C++ 字符串存储ASCII 码字W,而不是标准码Unicode 字符
2. C++ 字符串是可以被修改的Q而Java字符串的内容是不可修改的(immutable)?/p>
3. 取子字符串的操作?C++ 中叫?substrQ这个命?tt>s.substr(i, n)从字W串s中取得从位置 i 开始长度ؓ(f)n的子字符丌Ӏ?/p>
4. 在C++中,你只能够字W串与其它字W串对象怸?concatenate)Q而不能够与Q意的对象怸联?/span>
5. C++中可以直接用关pL作符 ==?!=?<?<=?>?>= 来进行字W串比较Q?/span>其中后面四个操作W是按字母顺序进行比较的。这比Java中用函数equals和compareTo来比较要方便很多?/p>
在C++中,本地变量的定义看h与Java中相同,例如Q?/p>
int n = 5;
实际上这正是C++和Java的一个重要不同之处。C++~译器不Ҏ(gu)地变量进行初始化(g)验,所以在C++中很Ҏ(gu)忘记初始化一个变量,q种情况下,变量的D变量所占内存区域中刚好当前存在随机倹{这昄是很Ҏ(gu)产生E序出错的地斏V?/span>
与Java一P C++中类可以有数据域和静(rn)态变量。不同的是,C++中变量可以在函数甚至是类的外面定义,q些所谓的全局变量可以在程序的M函数中被讉KQ因而不易被很好的管理?strong>所C++中应该尽量避免用全局变量?/span>
在C++中,帔R可以在Q何地方被定义Q记得在Java中,帔R必须是类的静(rn)态数据static data)?strong> C++ 使用关键?const来定义常量,而Java中是 final。例如:(x)
const int DAYS_PER_YEAR = 365;
C++ 中对cȝ定义与Java有些不同Q这里是一个例子:(x)一个C++ 版本?Pointc?
class Point /* C++ */
{
public:
Point();
Point(double xval, double yval);
void move(double dx, double dy);
double getX() const;
double getY() const;
private:
double x;
double y;
};
q里几点重要的不同是Q?/p>
1. C++的类定义中分为公共和U有部分Q分别以关键?public?private开始。而在Java中,每一个元素都必须标明 public?private?/p>
2. C++中类的定义只包含函数的声明,真正的实现另外单独列出?/span>
3. 讉K函数(accessor methods)标有关键?constQ表明这个函C?x)改变本对象的元素倹{?/span>
4. cd义的l尾处有分号
cM函数的实现跟在类的定义之后。因为函数是在类外面定义的,所以每一个函数的名字前面要加cdUC为前~Qƈ使用操作W双冒号::来分割类的名U和函数的名U?/span>。不改变隐含参数|卛_前对象的|(j)的访问函数用 const标明。如下所C是上面cd义中的函数的实现Q?/p>
Point::Point() { x = 0; y = 0; }
void Point::move(double dx, double dy)
{
x = x + dx;
y = y + dy;
}
double Point::getX() const
{
return x;
}
Java ?C++ 最主要的不同在于对象变量的使用?/span>
?C++中,对象变量存储的是真正的对象的|而不是对象引?Point p(1, 2); /* 构造对?p */
如果不跟参数赋|则用默认构造函敎ͼ例如Q?/p>
Time now; /* 默认使用构造函?Time::Time() */
q一点与Java很不同。在Java中,q个命o(h)仅仅生成一个没有初始化的对象referenceQ要创徏对象q得使用new在堆中创Z个实际的对象.
而在C++中,它生成一个实际的对象?/span>
当一个对象被赋给另一个对象变量的时候,实际的值将被拷贝。而在Java中,拯一个对象变量只不过是徏立了(jin)另外一个指向对象的reference。拷贝一个C++的对象就像在Java中调用cloneq个函数一P而修Ҏ(gu)贝的g?x)改变原对象的倹{?/span>例如Q?/p>
Point q = p; /* 拯p到q ,q是另外一个新的对象,有自q内存I间*/
q.move(1, 1); /* Udq而p不动Q即q的值变?sh)(jin),而p的不?/
多数情况下,C++中这U对象直接对值操作的Ҏ(gu)用v来很方便Q但是也有些时候不如人意Q?/p>
C++中对象的指针非常cM于Java中的引用
1. 当需要一个函C修改一个对象的|必须C要用按引用调用call by reference (参见下面函数部分)Q或者将参数定义为指针类?在函C通过指针来操作其实际指向的对?/span>
2. 两个对象变量不能指向同一个对象实体。如果你要在C++中实现这U效果,必须使用指针pointerQ参见下面指针部分)(j)
3. 一个对象变量只能存储一U特定的cd的|如果你想要用一个变量来存储不同子类的对象的|多态ploymorphism)Q则需要用指针?/span>
4. 如果你想在C++中用一个变量来或者指向null或者指向一个实际的对象Q则需要用指?/span>
在Java中,每一个函数必L者是对象函数(instance method)Q或者是?rn)态函?static function)或称cd数。C++同样支持对象函数和静(rn)态函敎ͼcd敎ͼ(j)Q但同时C++也允许定义不属于Mcȝ函数Q这些函数叫?strong>全局函数Qglobal functionsQ?/span>?/span>
特别的是Q每一个C++ E序都从一个叫?main的全局函数开始执行:(x)
int main()
{ . . .
}
q有另外一个格式的main函数可以用来捕捉命o(h)行参敎ͼcM于Java的main函数Q但是它要求关于C格式的数l和字符串的知识Q这里就不介l了(jin)?/p>
按照?fn)惯Q通常如果E序执行成功Q?main函数q回0Q否则返回非零整数?/p>
同Java一P函数参数是通过g递的(passed by value)。在Java中,函数无论如何都是可以修改对象(cd?的值的Q?/span>因ؓ(f)java中传递的是java中的引用,相当与C++中的指针。Java函数q回时也是返回的引用,此引用拯到外面的某个引用变量?/span>Q。然而在C++中,因ؓ(f)对象直接存储的是实际的?/span>Q?/span>而不是指向值的referenceQ?/span>也就是说传入函数的是一个实际值的拯Q因此也无法修改原来对象的倹{?C++中函数返回时也是要拷贝到外面的变量中?是指针就拯指针Q是对象p行对象的拯)
所以,C++ 有两U参C递机Ӟ同Java一L(fng)按D?call by value) Q以?qing)按地址调用(call by reference)。当一个参数是按reference传递时Q函数可以修改其原始倹{Call by reference 的参数前面有一个地址?&跟在参数cd的后面,例如Q?/p>
void raiseSalary(Employee& e, double by)
{ . . .
}
下面是一个典型的利用call by reference的函敎ͼ在Java中是无法实现q样的功能的?/span>
void swap(int& a, int& b)
{ int temp = a;
a = b;
b = temp;
}
如果使用 swap(x, y)来调用这个函敎ͼ则reference参数 a?b指向原实际参?tt>x?y的位|,而不是它们的值的拯Q因此这个函数可以实现实际交换这两个参数的倹{?/p>
?C++中,每当需要实C改原参数的值时你就可以使用按地址调用 call by reference ?/span>
C++ 的向量结构结合了(jin)Java中数l和向量两者的优点。一个C++ 的向量可以方便的被访问,其容量又可以动态的增长。如?T是Q意类型,?vector<T>是一个元素ؓ(f) Tcd的动态数l。下面的语句
vector<int> a;
产生一个初始ؓ(f)I的向量。而语?/p>
vector<int> a(100);
生成一个初始有100个元素的向量。你可以使用push_back函数来添加元素:(x)
a.push_back(n);
调用 a.pop_back()?tt>a中取出最后一个元素(操作后这个元素被从a中删?Q?使用函数size可以得到当前a中的元素个数?/p>
你还可以通过我们熟?zhn)?[]操作W来讉K向量中元素,例如Q?/p>
for (i = 0; i < a.size(); i++) {
sum = sum + a[i];
}
同Java中一P数组索引必须?0 ?tt>a.size() - 1之间的倹{但是与Java不同的是QC++中没有runtime的烦(ch)引号合法性检验。试图访问非法的索引位置可能造成非常严重的出错?/p>
像所有其?C++ 对象一P向量也是倹{如果你一个向量赋值给另外一个向量变量,所有的元素都会(x)被拷贝过厅R?/p>
vector<int> b = a; /* 所有的元素都被拯?*/
Ҏ(gu)Java中的情况Q在Java中,一个数l变量是一个指向数l的reference。拷贝这个变量仅仅生另外一个指向同一数组的referenceQ而不?x)拷贝每一个元素的倹{?/p>
正因如此Q如果一个C++函数要实C改向量的|必须使用reference参数Q?/p>
void sort(vector<int>& a)
{ . . .
}
在C++中,标准的输入输出流用对?cin?cout表示。我们?<<操作W写输出Q例如:(x)
cout << “Hello, World!”;
也可以连着输出多项内容Q例如:(x)
cout << “The answer is ” << x << “"n”;
我们使用 >>操作W来d一个数字或单词Q例如:(x)
double x;
cout << “Please enter x: “;
cin >> x;
string fname;
cout << “Please enter your first name: “;
cin >> fname;
函数getline可以d整行的输入,例如Q?/p>
string inputLine;
getline(cin, inputLine);
如果到达输入的结,或者一个数字无法被正确的读入,q个对象会(x)被设|ؓ(f) failed 状态,我们可以使用函数 fail来检验这个状态,例如Q?/p>
int n;
cin >> n;
if (cin.fail()) cout << “Bad input”;
一旦一个流的状态被设ؓ(f)failedQ我们是很难重置它的状态的Q所以如果你的程序需要处理错误输入的情况Q应该用函?getline然后人工处理得到的输入数据?/p>
我们已经知道在C++中,对象变量直接存储的是对象的倹{这是与Java不同的,在Java中对象变量存储的是一个地址Q该地址指向对象值实际存储的地方。有时在C++中也需要实现这L(fng)布置Q这qC(jin)指针pointer。在 C++中,一个指向对象的变量叫做指针。如果T是一U数据类型,?T*是指向这U数据类型的指针?/span>
像 Java中一P一个指针变量可以被初始化ؓ(f)I?NULLQ另外一个指针变量的|或者一个调用new生成的新对象Q?/p>
Employee* p = NULL;
Employee* q = new Employee(”Hacker, Harry”, 35000);
Employee* r = q;
实际上在C++中还有第四种可能Q那是指针可以被初始化为另外一个对象的地址Q这需要用地址操作W?&Q?/span>
Employee boss(”Morris, Melinda”, 83000);
Employee* s = &boss;
q实际上q不是什么好L。保险的做法q是应该直接让指针指向?new生成的新对象?/span>
到目前ؓ(f)止,C++ 指针看v来非常像 Java 的对象引用变?/span>。然而,q里有一个很重要的语法的不同?/span>我们必须使用星号操作W?*来访问指针指向的对象。如?p是一个指?tt>Employee对象的指针,?*p才代表了(jin)q个对象Q?/p>
Employee* p = . . .;
Employee boss = *p;
当我们需要执行对象的函数或访问对象的一个数据域Ӟ也需要?*pQ?/p>
(*p).setSalary(91000);
*p外面的括h必需的,因ؓ(f) .操作W比 * 操作W有更高的优先。C的设计者觉得这U写法很隄Q所以他们提供了(jin)另外一U替代的写法Q?-> 操作W来实现 *?.操作W的l合功能。表辑ּ
p->setSalary(91000);
可以调用对象*p的函?setSalary。你可以单的C.操作W是在对象上使用的,-> 操作W是在指针上使用的?/span>
如果你不初始化一个指针,或者如果一个指针ؓ(f)I?NULL 或指向的对象不再存在Q则在它上面使用 *?->操作W就?x)出错?不幸的是 C++ runtime pȝq不(g)查这个出错。如果你范了(jin)q个错误Q你的程序可能会(x)行ؓ(f)古怪或L?/span>
而在Java中,q些错误是不?x)发生的。所有的reference都必d始化Q所有的对象只要仍有reference指向它就不会(x)被从内存?sh)清除,因此你也不?x)有一个指向已被删除的对象的reference。Java的runtime pȝ?x)检查reference是否为空Qƈ在遇到空指针时抛Z个null pointer的例?exception)?/p>
C++ ?Javaq有一个显著的不同Q就?Java ?em>垃圾回收功能Q能够自动回收被废弃的对象。而在C++中,需要程序员自己理内存分配回收?/span>
C++中当对象变量出范围时可以自动被回收。但是用new生成的对象必ȝdelete操作W手动删除,例如Q?/p>
Employee* p = new Employee(”Hacker, Harry”, 38000);
. . .
delete p; /* 不在需要这个对?*/
如果你忘记删除一个对象,那么你的E序有可能最l用光所有内存。这是我们常说的内存泄?(memory leak)。更重要的是Q如果你如果删除?jin)一个对象,然后又l用它Q你可能覆盖不属于你的数据。如果你刚y覆盖?jin)用于处理内存回收的数据域,那么内存分配机制可能运转失常而造成更严重的错误Q而且很难诊断和修复。因此,在C++中最好尽量少用指针?/p>
C++和Java中承的基本语法是很怼的。在C++中,使用 : public代替Java中的extends来表C承关p??(C++ 也支持私有承的概念Q但是不太有用?
默认情况下,C++中的函数不是动态绑定的。如果你需要某个函数实现动态绑定,需要?tt>virtual声明它ؓ(f)虚函敎ͼ例如Q?/p>
class Manager : public Employee
{
public:
Manager(string name, double salary, string dept);
virtual void print() const;
private:
string department;
};
同Java一P构造函C调用父类的构造函数有Ҏ(gu)的语法?Java使用关键?super。C++中必d子类的构造函C外调用父cȝ构造函数。下面是一个例子:(x)
Manager::Manager(string name, double salary, string dept)
: Employee(name, salary) /* 调用父类的构造函?*/
{ department = dept;
}
Java 中在子类函数中调用父cȝ函数时也使用关键?super。而在C++中是使用父类的名U加上操作符 ::表示Q例如:(x)
void Manager::print() const
{ Employee::print(); /* 调用父类的函?*/
cout << department << “"n”;
}
一?C++ 对象变量只能存储特定cd的对象倹{要惛_C++中实现多?polymorphism)Q必M用指针。一?T*指针可以指向cd?T?T的Q意子cȝ对象Q例如:(x)
Employee* e = new Manager(”Morris, Melinda”, 83000, “Finance”);
你可以将父类和不同子cȝ对象混合攉C个元素均为指针的向量中,然后调用动态绑定的函数Q如下所C:(x)
vector<Employee*> staff;
. . .
for (i = 0; i < staff.size(); i++)
staff[i]->print();