1、const关键字
(1)可以用const来确保方法不修改参数:
Star::Star(const char * s){...} //won't change the string to which s points
(2)可以用const来确保方法不修改调用它的对象:
void Star::show()const{...} //won't change invoking object
这里const表示const Star * this,而this指向调用的对象
(3)通常,可以将返回引用的函数放在赋值语句的左侧,这实际意味着可以将值赋给引用的对象。但可以使用const来确保引用或指针返回的值不能用于修改对象中的数据:
const Stock & Stock::topval(const Stock & s)const
{
if(s.total_bal>total_val)
return s; //argument object
else
return *this; //invoking object
}
该方法返回this或s的引用。因为this和s都被声明为const,所有函数不能对他们进行修改,这意味着返回的引用也必须被声明为const。
注意,如果函数将参数声明为指向const的指针或引用,则不能将该参数传递给另一个函数,除非后者也确保了参数不会被修改。
//version 1
Vector Max(const Vector & v1,const Vector & v2)
{
if(v1.magval()>v2.magval())
return v1;
else
return v2;
}
//version 2
const Vector & Max(const Vector & v1,const Vector & v2)
{
if(v1.magval()>v2.magval())
return v1;
else
return v2;
}
- 返回对象将调用它的复制构造函数,而返回引用不会。调用复制构造函数时,可能会创建一个新的临时对象,在该临时变量过期时会自动调用析构函数将其删除释放内存。
- v2和v2都被声明为const引用,因此返回类型必须为const,这样才匹配。
- 如果被返回的对象是被调用函数中的局部变量,则不应按引用方式返回它,因为在被调用函数执行完毕时,局部对象将调用析构函数。
2、静态变量
//twofile1.cpp
#include<iostream>
int tom=3;
int dick=30;
static int harry=300;
void remote_access();
int main()
{
using namespace std;
cout<<"main() reports the following addresses: \n";
cout<<&tom<<" = &tom, "<<&dick<<" =&dick , ";
cout<<&harry<<" =&harry, \n";
remote_access();
return 0;
}
//twofile2.cpp
#include<iostream>
extern int tom;
static int dick=10;
int harry=200;
void remote_access()
{
using namespace std;
cout<<"remote_access() reports the following addresses: \n";
cout<<&tom<<" =&tom, "<<&dick<<" =&dick, ";
cout<<&harry<<" =&harry\n";
}
- 关键字static被用在作用域为整个文件的声明时,表示内部链接性;被用于局部声明中时,表示局部变量的存储持续性为静态的。
- 如果声明的静态变量的名称与另一个文件中声明的常规变量相同,则在该文件中,静态变量将隐藏常规变量。
- 在C++标准中,不赞成在名称空间和全局作用域中使用static关键字(该标准使用“不赞成”表明,这种做法目前合法,但以后修订标准时,很可能将其视为非法)。
- 在默认情况下全局变量的链接性为外部的,但const全局变量的链接性为内部的。即,在C++中使用const关键字就像是 使用static关键字一样。在声明const变量时,必须进行初始化。与常规变量不同的是,可以初始化extern const变量。 如:extern const int states=50;
- 声明的外部变量,其作用域为当前文件,表示内部链接性;但是可以使用extern关键字访问其他文件中声明的外部变量。
- 不能在多个文件中定义同一个全局变量。即,只能有一个文件可以包含前面的声明,而其他文件中必须使用关键字extern来提供引用声明。另外,只有未使用extern关键字的声明才能被初始化。
(2) 不能在未命名名称空间所属文件之外的其他文件中,使用该名称空间中的名称,因此这种方法可以替代连接性为内部的静态变量。通常在调用的函数的中声明静态变量。
故,假设有如下代码:
static int counts;
int other();
int main()
{
...
}
int other()
{
...
}
按照C++标准,程序员应该这样书写代码:
namespace
{
int counts;
}
int other();
int main()
{
...
}
int other()
{
...
}
在未命名的名称空间中声明的名称的潜在作用域为:从声明点到该声明点区域末尾。(功能同全局变量)
using声明使一个名称可用,而using编译指令(由名称空间和它前面的关键字using namespace组成)使所有的名称都可用。
如:using std::cout;//using 声明
using namespace std;//using 编译指令
(3) 类中使用枚举声明的变量,等同于使用static声明的静态全局变量。
如:
class Stonewt
{
private:
enum{Lbs_per_stn=14};
int stone;
double pds_left;
double pounds;
public:
Stonewt(double lbs);
Stonewt(int stn,double lbs);
Stonewt();
~Stonewt();
void show_lbs()const;
void show_stn()const;
};
其中的"enum{Lbs_per_stn=14};"等同于"static const int Lbs_per_stn=14;"
(4) 静态类static的深入理解:
//stringBad.h
#include<iostream>
class StringBad
{
private:
char * str; //pointer to string
int len; //length of string
static int num_strings; //number of objects
//enum{num_strings2=0};
//static const int num_strings3=0;
public:
StringBad(const char * s);
StringBad();
~StringBad();
friend std::ostream & operator<<(std::ostream & os,
const StringBad & st);
};
//stringBad.cpp
#include<cstring>
#include"strngbad.h"
using std::cout;
int StringBad::num_strings=0;
StringBad::StringBad(const char *s)
{
len=std::strlen(s);
str=new char[len+1];
std::strcpy(str,s);
num_strings++;
cout<<num_strings<<": \""<<str
<<"\" object created\n";
}
StringBad::StringBad()
{
len=4;
str=new char[4];
std::strcpy(str,"C++");
num_strings++;
cout<<num_strings<<": \""<<str
<<"\"default object created\n";
}
StringBad::~StringBad()
{
cout<<"\""<<str<<"\" objet deleted, ";
--num_strings;
cout<<num_strings<<" left\n";
delete [] str;
}
std::ostream & operator<<(std::ostream & os,const StringBad & st)
{
os<<st.str;
return os;
}
//useStringBad.cpp
#include<iostream>
#include "strngbad.h"
using std::cout;
void callme1(StringBad &);
void callme2(StringBad &);
int main()
{
using std::endl;
StringBad headline1("Celery Stalks at Midnight");
StringBad headline2("Lettuce Prey");
StringBad sports("Spinach Leaves Bowl for Dollars");
cout<<"headline1: "<<headline1<<endl;
cout<<"headline2: "<<headline2<<endl;
cout<<"sports: "<<sports<<endl;
callme1(headline1);
cout<<"headline1: "<<headline1<<endl;
callme2(headline2);
cout<<"headline2: "<<headline2<<endl;
cout<<"Initialize one object to another: \n";
StringBad sailor=sports;
cout<<"sallor: "<<sailor<<endl;
cout<<"Assign one object to another: \n";
StringBad knot;
knot=headline1;
cout<<"knot: "<<knot<<endl;
cout<<"End of main()\n";
return 0;
}
void callme1(StringBad & rsb)
{
cout<<"String passed by reference: \n";
cout<<" \""<<rsb<<"\"\n";
}
void callme2(StringBad & sb)
{
cout<<"String passed by value: \n";
cout<<" \""<<sb<<"\"\n";
}
- 不能在类声明中初始化静态数据成员,除非静态数据成员为枚举或static const int valuename;
- 字符串独立保存在堆内存中,对象仅保存了指出到哪里去查找字符串的信息。
- 类成员str是指针,构造函数必须分配足够的内存来存储字符串,然后将字符串复制到内存中。
- 当StringBad对象过期时,str指针也将过期。但str指向的内存任被保存,必须使用delete将其释放。删除对象可以释放对象本身占用的内存,但是不能自动释放属于对象成员的指针指向的内存。
- 在构造函数中使用new来分配内存时,必须在相应析构函数中使用delete来释放内存。如果使用new[](包括中括号)来分配内存,则应使用delete[](包括中括号)来释放内存。
3、外部变量声明extern
在函数体外声明的普通变量为外部全局变量,可以被其他文件使用;在函数体外声明的静态变量为内部全局变量,作用域为当前源文件。
在函数中再次声明一个和外部全局变量名字相同的变量时,实际上是对该外部全局变量进行再次声明。可以使用关键字extern来重新声明以前定义过的外部变量。
#include<iostream>
using namespace std;
double warming=0.3;
void update(double dt);
void local();
int main()
{
cout<<"Global warming is "<<warming<<" degrees.\n";
update(0.1);
cout<<"Global warming is "<<warming<<" degrees.\n";
local();
cout<<"Global warming is "<<warming<<" degrees.\n";
return 0;
}
void update(double dt)
{
extern double warming; //该变量其实就是重新声明了外部声明的全局变量warming。关键字extern可以被省略
warming+=dt;
cout<<"Updating global warming to "<<warming;
cout<<" degrees.\n";
}
void local()
{
double warming=0.8;
cout<<"Local warming = "<<warming<<" degrees.\n";
cout<<"But gloabal warming = "<<::warming;
cout<<" degrees.\n";
}
要正确理解变量的初始化。初始化指的是在分配内存单元时给它赋值。故只能在声明将为变量分配存储空间时(即定义声明),才能在声明中初始化变量。
extern double warming=0.5; //INVALID。不能在引用声明中初始化变量。
4、explicit关键字
只接受一个参数的构造函数定义了从参数类型到类类型的转换。如果使用关键字explicit限定了这种构造函数,则它只能用于显示转换,否则也可以用于隐式转换。
//stone.h
class Stonewt
{
private:
enum{Lbs_per_stn=14};
int stone;
double pds_left;
double pounds;
public:
Stonewt(double lbs);
Stonewt(int stn,double lbs);
Stonewt();
~Stonewt();
void show_lbs()const;
void show_stn()const;
};
//stone.cpp
#include<iostream>
#include"stonewt.h"
using std::cout;
Stonewt::Stonewt(double lbs)
{
stone=int(lbs)/Lbs_per_stn;
pds_left=int(lbs)%Lbs_per_stn+lbs-int(lbs);
pounds=lbs;
}
Stonewt::Stonewt(int stn, double lbs)
{
stone=stn;
pds_left=lbs;
pounds=stn*Lbs_per_stn+lbs;
}
Stonewt::Stonewt()
{
stone=pounds=pds_left=0;
}
Stonewt::~Stonewt()
{
}
void Stonewt::show_stn()const
{
cout<<stone<<" stone, "<<pds_left<<" pounds\n";
}
void Stonewt::show_lbs()const
{
cout<<pounds<<" pounds\n";
}
//useStone.cpp
#include<iostream>
#include"stonewt.h"
using std::cout;
void display(const Stonewt st,int n);
int main()
{
Stonewt pavarotti=260;
Stonewt wolfe(285.7);
Stonewt taft(21,8);
cout<<"The tenor weighed ";
pavarotti.show_stn();
cout<<" The detective weighed ";
wolfe.show_lbs();
pavarotti=256.8;
taft=325;
cout<<"After dinner, the tenor weighed ";
pavarotti.show_stn();
cout<<"After dinner , the President weighed ";
taft.show_lbs();
display(taft,2);
cout<<"The wrestler weighed even more.\n";
display(422,2);
cout<<"No stone left unearned \n";
return 0;
}
void display(const Stonewt st, int n)
{
for(int i=0;i<n;i++)
{
cout<<"Wow! ";
st.show_stn();
}
}
C++允许指定类和基本类型之间进行转换的方式。首先,任何接收唯一一个参数的构造函数都可被用作转换函数,将类型与该参数相同的值转换为类。如果将类型与该参数相同的值赋给对象,则C++将自动调用该构造函数。例如,假设有一个String类,它包含一个将char * 值作为唯一参数的构造函数,那么如果bean是String对象,则可以使用下面的语句:
bean="pinto"; //converts type char * to type String
不过,如果在该构造函数的声明前加上了关键字explicit,则该构造函数将只能用于显式转换:
bean=String("pinto");//converts tyype char * to type String explicitly
5、this指针
this指针时类方法可以使用的指针,它指向用于调用方法的对象。因此,this时对象的地址,*this是对象本身。
//mytime2.h
class Time
{
private:
int hours;
int minutes;
public:
Time();
Time(int h,int m=0);
void AddMin(int m);
void AddHr(int h);
void Reset(int h=0,int m=0);
Time operator+(const Time & s) const;
Time operator-(const Time & s) const;
Time operator*(double n) const;
void Show() const;
};
//mytime2.cpp
#include<iostream>
#include"mytime2.h"
Time::Time()
{
hours=minutes=0;
}
Time::Time(int h, int m)
{
hours=h;
minutes=m;
}
void Time::AddMin(int m)
{
minutes+=m;
hours=hours+minutes/60;
minutes=minutes%60;
}
void Time::AddHr(int h)
{
hours+=h;
}
void Time::Reset(int h, int m)
{
hours=h;
minutes=m;
}
Time Time::operator +(const Time & s) const
{
Time sum;
sum.minutes=minutes+s.minutes;
sum.hours=hours+s.hours+sum.minutes/60;
sum.minutes=sum.minutes%60;
return sum;
}
Time Time::operator -(const Time & s) const
{
Time diff;
int tot1,tot2;
tot1=s.minutes+60*s.hours;
tot2=minutes+60*hours;
diff.minutes=(tot2-tot1)%60;
diff.hours=(tot2-tot1)/60;
return diff;
}
Time Time::operator *(double mult) const
{
Time result;
long totalminutes=hours*mult*60+minutes*mult;
result.hours=totalminutes/60;
result.minutes=totalminutes&60;
return result;
}
void Time::Show() const
{
std::cout<<hours<<" hours, "<<minutes<<" minutes.\n";
}
//user.cpp
#include<iostream>
#include"mytime2.h"
int main()
{
using std::cout;
using std::endl;
Time weeding(4,35);
Time waxing(2,47);
Time total;
Time diff;
Time adjusted;
cout<<"weeding time= ";
weeding.Show();
cout<<endl;
cout<<"waxing time= ";
waxing.Show();
cout<<endl;
cout<<"total work time= ";
total=weeding+waxing;
total.Show();
cout<<endl;
diff=weeding-waxing;
cout<<"weeding time=waxing time = ";
diff.Show();
cout<<endl;
adjusted=total*1.5;
cout<<"adjusted worked time = ";
adjusted.Show();
cout<<endl;
return 0;
}
程序中使用了操作符重载
每个成员函数(包括构造函数和析构函数)都有一个this指针。this指针指向调用对象。如果方法需要引用整个调用对象,则可以使用表达式*this。在函数的括号后面使用const限定符将this限定为const,这样将不能使用this来修改对象的值。