从C转入C++

从C转入C++

从C转入C++
C++是C的超集(C的扩展名一般为.CPP),在大都分时候,C程序可在C++编译器下运行,但有时会有问题,主要有以下几点:

在C++中可用//表示该符号后面为注释,如:
void main() //这是主函数
在C中以可用以下标识符,但在C++中它们是关键字: asm bed_cast bad_typeid catch
class const_cast delete dymanic_cast
except finally friend inline
namespace new operator private
protected public reinterpret_cast static_cast
template this throw try
type_info typeid using virtual
xalloc

在C中,可在前面无声明和定义时,调用函数(虽会警告). 但在C++中,任何函数在调用前必须声明或定义.
在C中声明函数时,可不带参数,如:
int FuncA();//实际可能有几个参数
而在C++中这仅能中明无参数的函数。
在C中可用旧式的函数定义如:
int FuncA(x,y)
int x;
int y;
{
...
}
而在C++中这是不允许的.
在C中可对任何指针赋void指针,下:
int a;
int *pint;
void *pvoid=&a
pint=pvoid; //C中可以,C++错
C++会出错,因C++无法自动将Word指针变成另一指针类型,可显式转换
pint=(int*)pvoid;
C中enum,srtuct,union可和同范围内的typedef名一致,而在C++由于可只用名称引用struct等结构,如:
struct TypeA
{
...
};
则可用 TypeA x;定义结构型变量x。
固不能与typedef同名,否则编译器不清楚你到底想要定义什么。
另sizeof()也有两个不同点:在C中sizeof('x'),在C中相当于sizeof(int),而在C++中相当于sizeof(char)。另如有枚举
eumu E(x,y,z);
sizeof(x)在C中相当于sizeof(int),而在C++中为sizeof(E)。
返回到页首


--------------------------------------------------------------------------------

C++新特性
局部变量的声明
在C中我们要声明一个局部变量,必须在块的开头任何语句前声明,在C++中你可在程序任何一个位置声明一个变量.因此一般你可在要用某个变量时才声明它,这样便于阅读与维护(你甚至可在for语句中定义)如:
for(int i=0;i<100;i++) //在C中i必须定义在开始处
{
int c=12;
c*=12;
int a; //这在C中可是不行的
...
}
范围分解操作符
我们都知道:在C中,如在内部(如函数内)声明一个与外部变量(如全局)相同的变量时,内部的变量将掩盖外部变量,(此时外部变量存在但不可见).而在C++中这个外部的变量也是可存取的,只要在该变量前加上"::"即可,如:
double a;
...
{
int a; //声明一与外部浮点变量a相同的整型变量a
a=3; //这个a为内部变量,整型的
::a=3.5; //a为外部,浮点型
}
内嵌函数(线上函数)
如在C++中用inline定义一个函数,如:
inline int FuncA(int a) { ...}
则统编译器把对这个函数的调用换成实际的函数码,和宏很类似,但有两个重要的优势:1.调用函数时,编译器检查参数类型,而宏不会.2.如果表达式传递给函数,它只会求一次值.而在宏中它求两次,这有可能带来问题,如:
ABS(--i); 若ABS是内嵌函数它和我们想的一样工作,而如ABS是宏,则i将被减两次,这可能并不是我们希望的.
这类函数主要用在进行大的循环中,以加快速度,或在类中使用.它与一般函数的不同的是,如改变了它,则所有调用它的源文件都要重新编译.
重载函数(我不知为什么有的书把重载称为过载,怪别扭的)
在C中如你要写一个求浮点型和整型变量绝对值的函数据,那你必须写两个不同的函数(虽然它们内部非常的象)就象下面这样:
int AbsI(int i) {...} //整型版的
double AbsD(double i) {...} //浮点版
而在调用时,不能搞错,而在C++中你可在同一程序中多次定义同一个函数(但它们不能有完全一样的形式),则上例可定义成:
int Abs(int i) {...} //整型版的
double Abs(double i) {...} //浮点版
这样在凋用时可用同一个函数名调用,程序会自动调用正确的版本,如:
Abs(3); //整型版
Abs(-3.5) //调浮点版
你也可能有时调用某函数,而每次用的参数个数不一样,也可利用该功能完成.
缺省函数参数
C++可在声明函数时定义缺省参数值来减少一些编程工作,如:
void ShowText(char *text,int length=-1,int color=0);
这样你可用如下的方法调用:
char *t="hello";
ShowText(t,6,3); //这和平时用法一样
ShowText(t,6); //省去一个参数,此时 color=0
ShowText(t);//省了两个参数, length=-1;color=0
调用时不能只省去中间的,如
ShowText(t,,4); //这是错的
它在定义时,还要注意:不能在参数表中部定义一个缺省变量,而它后面又是正常的变量,(要么它后面全是缺省变量,要么把它放在最后)如:
void ShowText(chat *text,int length=-1;int color);//错
缺省变量一经定义不可在同一范围内再定义(即使是一样的)如
void ShowText(char *text,int length=-1,int color=0);//声明
...
void ShowText(char *text,int length=-1,int color=0) //定义,这就是错的
{...}
另:在用缺省变量和重载函数时要注意它们可能造成运行时出错(编译时可能没问题,如有以下定义:
void Display(char *buf);
void Display(char *buf,int length=30);
这看上去没有任何问题,但如有以下的调用,则出错;
Display("hello"); //多义调用,程序不知到底应调哪一个
引用类型
声明为引用的变量其实是另一变量的别名,可用&定义一引用,如
int c;
int &b;
b=c;
此时b为c的别名,即c与b有同样的地址,对b的操作和对c操作是一个效果.它与指针不同,它们是同样的类型.不能将常量赋值给它:如 b=5;//错
也可将函数参数或返回类型声明为引用,如将参数设为引用,如下
int FuncA(int &a);
而有以下调用:
FuncA(b); //则如在函数内改变了a的值,则b的值也发生同样的改变
当然此时不能有如下的调用(把常量赋给了变量)
Func(4);
这除了用于能改变参数的值之外,也常用于参数为结构时(此时不用将整个结构再复制一遍,有利于提高效率,此时最好也将之定义为常量(见后常量介绍)
函数的返回值也可定义为引用,此时函数必须返回一个适当的变量,如:
int a=0;
int &GetA() {return a}
void main()
{
int N;
N=GetA(); //N=0,注N并不是a的引用
GetA()=5; //a=5;相当与a=5
++GetA(); //a=6;相当于++a
}
由于函数的引用是在函数返回后用的,它不能返回已不存在的变量,特别是返回去自动变量,或是参数的引用,如下例是危险的;
int &aa()
{int a;
return a;}
int &bb(int b)
{return b}
这个程序并不产生编译错误,但它们的结果是无法预料的.这类函数可安全的返回全局变量,static,或动态变量(在下面的new/deldet中介绍)
new和delete
C++中一般用new和delete分配和回收内存块,使用new时指定数据类型,由系统分配足以存放指定类型对象(可为各种类型)的内存块,返回该块地址作为该指定类型的指针,如:
int *cc;
char *ch;
cc=new int;
ch=new char[23]; //分配了一个 char型数组(有23个项)
它们可由delete回收,如:
delete cc;
delete[] ch; //[]表示回收整个数组,而不是单个元素.
常量
可用const定义存放常量的变量,如
const a=100;
则a是一个常量,它不能在程序中改变,但常量并不象你想象的那么简单,它并不象#defind:
一.常量和指针:
这也有几种:
1.将指针定义为常量,可随时改变指针的值,但不能改变指针指向变量的值,(相当于只读指针),如:
const int *PCI;
const int a=1;
int b=2;
PCI=&a; //OK!
*PCI=5; //错,不可改动
PCI=&b; //OK!
2.将常量指针定义到非常量变量,此时指针为常量,必须在定义时初始化,此后不能再赋其它地址,但指针指向的变量可变,如:
int a=1;
int b=2;
int *const CPI=&a; //注意这行!
*CPI=5; //OK
CPI=&b; //ERROR,不能改变指针!
3.定义常量指针到常量对象.这时必须在定义指针时初始化,既不能改指针,也不能改指针指向的值.
4.不能将常量的地址赋于非常量的指针
二.常量与引用
可以定义常量对象的引用.它可用常量初始化:
const int a=1;
const int &RCA=a;
也可用变量初始化
int b;
const int &RCB=b;
无论如何都不能用引用改变所指变量的值.
三.常量和函数
定义函数的参数或返回类型为常量.
如将参数中的一般变量定义为常量,没有什么意义,如:
FuncA(const int a); //a本来就不能改变
我们知道若参数为指针或是引用,则这可改变这此变量的值,但有时,我们可能只对参数的值感兴趣(而不想改变它们的值)这时,可用常量定义参数,保证变量的改变不会有什么副作用,如:
funcA(const int *P);
funcB(const int &C); //无论函数如何对P,C操作,原来的值都不会改变.
另前面讲过如函数的参数是引用,则不能用常数为参数调用该函数,但如定义为常量引用,则可以, 如:
void FuncA(const int &r);
void main()
{...
FuncA(5); //还记得前面说的么,如它只是引用,这是错的,而现在它没问题
...}
如函数的返回值是一般变量,则定义返回为常量没有什么意义,但如是指针,或是引用,则使函数不能用返回值来改变指向或引用的变量.例:
const int *funcA()
{ static int p=1; //注意这的static,若为普通变量很危险,见前引用处
++p;
return &p}

const int &funcB()
{static int s=100;//同想请注意这个static
--s;
return s;}

void main()
{int N;
N=*funcA(); //OK,N为一拷贝
N=funcB(); //OK,N也为一拷贝
*funcA()=5; //错,不可改变.
++funcB(); //错,这可参考一下前面"引用"的例子
 

 

作者Blog:http://blog.csdn.net/Cloudyshadow/

定义函数对象

时间:2001/02/07 15:22 作者:vckbase VC知识库


  尽管函数指针被广泛用于实现函数回调,但C++还提供了一个重要的实现回调函数的方法,那就是函数对象。函数对象(也称“算符”)是重载了“()”操作符的普通类对象。因此从语法上讲,函数对象与普通的函数行为类似。


用函数对象代替函数指针有几个优点,首先,因为对象可以在内部修改而不用改动外部接口,因此设计更灵活,更富有弹性。函数对象也具备有存储先前调用结果的数据成员。在使用普通函数时需要将先前调用的结果存储在全程或者本地静态变量中,但是全程或者本地静态变量有某些我们不愿意看到的缺陷。

其次,在函数对象中编译器能实现内联调用,从而更进一步增强了性能。这在函数指针中几乎是不可能实现的。


下面举例说明如何定义和使用函数对象。首先,声明一个普通的类并重载“()”操作符:


class Negate

{

public:

int operator() (int n) { return -n;}

};


重载操作语句中,记住第一个圆括弧总是空的,因为它代表重载的操作符名;第二个圆括弧是参数列表。一般在重载操作符时,参数数量是固定的,而重载“()”操作符时有所不同,它可以有任意多个参数。


因为在Negate中内建的操作是一元的(只有一个操作数),重载的“()”操作符也只有一个参数。返回类型与参数类型相同-本例中为int。函数返回与参数符号相反的整数。


使用函数对象


我们现在定义一个叫Callback()的函数来测试函数对象。Callback()有两个参数:一个为int一个是对类Negate的引用。Callback()将函数对象neg作为一个普通的函数名:


#include

using std::cout;


void Callback(int n, Negate & neg)

{

int val = neg(n); //调用重载的操作符“()”

cout << val;

}


不要的代码中,注意neg是对象,而不是函数。编译器将语句


int val = neg(n);


转化为


int val = neg.operator()(n);


通常,函数对象不定义构造函数和析构函数。因此,在创建和销毁过程中就不会发生任何问题。前面曾提到过,编译器能内联重载的操作符代码,所以就避免了与函数调用相关的运行时问题。


为了完成上面个例子,我们用主函数main()实现Callback()的参数传递:


int main()

{

Callback(5, Negate() ); //输出 -5

}


本例传递整数5和一个临时Negate对象到Callback(),然后程序输出-5。


模板函数对象


从上面的例子中可以看出,其数据类型被限制在int,而通用性是函数对象的优势之一,如何创建具有通用性的函数对象呢?方法是使用模板,也就是将重载的操作符“()”定义为类成员模板,以便函数对象适用于任何数据类型:如double,_int64或char:


class GenericNegate

{

public:

template T operator() (T t) const {return -t;}

};


int main()

{

GenericNegate negate;

cout<< negate(5.3333); // double

cout<< negate(10000000000i64); // __int64

}


如果用普通的回调函数实现上述的灵活性是相当困难的。


标准库中函数对象


C++标准库定义了几个有用的函数对象,它们可以被放到STL算法中。例如,sort()算法以判断对象(predicate object)作为其第三个参数。判断对象是一个返回Boolean型结果的模板化的函数对象。可以向sort()传递greater<>或者less<>来强行实现排序的升序或降序:


#include // for greater<> and less<>

#include //for sort()

#include

using namespace std;


int main()

{

vector vi;

//..填充向量

sort(vi.begin(), vi.end(), greater() );//降序( descending )

sort(vi.begin(), vi.end(), less() ); //升序 ( ascending )

}

 

 

作者Blog:http://blog.csdn.net/ghj1976/

 

 

 

 

 


C++ articles:Guru of the Week #4 -- Class Mechantics
出处 http://www.gotw.ca

作者:Hub Sutter
译者:plpliuly

/*此文是译者出于自娱翻译的GotW(Guru of the Week)系列文章第3篇,原文的版权是属于Hub Sutter(著名的C++专家,"Exceptional C++"的作者)。此文的翻译没有征得原作者的同意,只供学习讨论。——译者
*/


#4 类的结构(Class Mechanics)
难度:7.5/10

你对定义一个类牵涉到的具体细节熟悉多少?这次GotW不仅讨论一些写一个类时很容易犯的错误,也要讨论怎样使你写的类具有专业风格.

问题:
假设你在看一段代码.其中包含如下一个类定义.这个类定义中有几处风格差劲,还有几处是的的确确的错误.你可以找到几处,该怎样修改?

class Complex {
public:
Complex( double real, double imaginary = 0 )
: _real(real), _imaginary(imaginary) {};

void operator+ ( Complex other ) {
_real = _real + other._real;
_imaginary = _imaginary + other._imaginary;
}

void operator<<( ostream os ) {
os << "(" << _real << "," << _imaginary << ")";
}

Complex operator++() {
++_real;
return *this;
}

Complex operator++( int ) {
Complex temp = *this;
++_real;
return temp;
}

private:
double _real, _imaginary;
};

答案:


这个类定义中出现的需要修改或改进的地方远不止我们在下面要提到的几处.我们提出这个问题的目的主要是为了讨论类定义的构成(比如,"<<操作符的标准形式是什么样的","+操作符是不是应该定义为成员函数?"),而不是讨论哪个接口的设计是不是太糟糕.但是,我还是想首先提一下属于后方面的建议,作为第0个值得修改的地方:

0.既然标准库中已经有复数类为什么还要自己定义一个Complex类呢?(而且,标准库中定义的类不会有下面提及的任何一个问题,是由业界中具有多年经验的最优秀的人编写.还是谦虚一点去直接重用这些类吧)
[忠告]尽量直接使用用标准库算法.那会比你自己动手写一个要快捷,而且不易出错.


class Complex {
public:
Complex( double real, double imaginary = 0 )
: _real(real), _imaginary(imaginary) {};
1.风格问题:这个构造函数可以当作单参数函数使用,这样就可能作为一个隐式转换函数使用.但是可能并不是所有时候发生的隐式转换都是你所期望的.
[忠告]当心不易察觉的类型转换操作在你并不希望的时候发生.一个好的避免办法就是:可能的话就用explicit修饰词限制构造函数.

void operator+ ( Complex other ) {
_real = _real + other._real;
_imaginary = _imaginary + other._imaginary;
}
2.风格问题:从效率上讲,参数应该是const&类型,而且"a=a+b"形式的语句应该换成"a+=b"的语句.
[准则]尽量使用const&参数类型代替拷贝传值类型参数.
[忠告]对于算术运算,尽量使用"a op= b"代替"a = a op b".(注意有些类--比如一个别人写的类--可能通过操作符的重载改变了op和op=之间的关系)

3.风格问题:+操作符不应该定义为成员函数.如果被定义成如上述代码中的成员函数,你就只能用"a=b+1"形式的语句而不能用形如"a=1+b"的语句.这样,你就需要同时提供operator+(Complex,int)和operator+(int,Complex)的定义.
[准则]在考虑把一个操作符函数定义为成员函数还是非成员函数的时候,尽量参照下面的原则:(Lakos96:143-144;591-595;Murray93:47-49)
- 一元(单参数)操作符函数应该定义成成员函数
- =,(),[],和->操作符应该定义成成员函数
- +=,-=,/=,*=,(等等)应该定义成成员函数
- 其余的操作符都应该定义成非成员函数

4.错误:+操作符不应该改变对象自身.它应该返回包含相加结果的临时对象.请注意,为了防止类似"a+b=c"的操作,这个操作符函数返回类型应该是"const Complex"(而不仅仅是"Complex").
(实际上,上述的代码更象定义+=操作符函数的代码)

5.风格问题:一般来讲,当定义了op的操作符函数,就应该同时定义了op=操作符.因此,这儿也应该定义+=操作符.上面的代码就应当是+=的定义(但是返回类型需要修改,参看下面的讨论).

void operator<<( ostream os ) {
os << "(" << _real << "," << _imaginary << ")";
}
(注意:对于一个真正的<<操作符,你应该检查stream的当前格式标志以支持一般的用法(译者:iostream通过设置标志来对输出格式进行控制).请参考你喜欢的STL的书籍了解细节)

6.错误:操作符<<不应该定义成成员函数(参看上面讨论),而且参数应该是"(ostream&,const Complex&)".注意,正如James Kanze指出,也尽量不要把它定义成友元函数!最好把它定义成通过调用一个"print"的public成员函数来工作.
7.错误:这个操作符函数应该返回"ostream&",而且应该以"return os"结束.这样就可以支持将多个输出操作链接起来(比如"cout << a << b;").
[准则]操作符<<和>>都应该返回stream的引用.

Complex operator++() {
++_real;
return *this;
}
8.风格问题:前自增应该返回Complex&,以便调用者可以更直接的操作.(译者:其实我觉得这不仅仅是风格的问题.因为按照前自增的标准定义,应该支持"++++a"的语法,而且两次前自增都应该是对a对象的自身操作,如果返回Complex类型,那第二次前自增调用的是临时对象的前自增操作.)

Complex operator++( int ) {
Complex temp = *this;
++_real;
return temp;
}
9.风格问题:后自增应该返回"const Complex".这可以防止形如"a++++"的用法.这句话可不会象某些人想当然那样会连续对a对象作两次自增操作.
10.风格问题:如果通过前自增操作来实现后自增操作符函数将会更好.(译者:将"++_real;"替换为"++(*this);")
[准则]尽量通过前自增操作来实现后自增操作.
private:
double _real, _imaginary;
};
11.风格问题:尽量避免使用以下划线开头命名变量.我曾经也很习惯这样定义变量,就连著名的"Design Patterns"(Gamma et al)中也是这样.但是因为标准库的实现中保留了很多下划线开头标识符,如果我们要用下划线开头定义自己的变量就得记住全部已经保留的标识符以免冲突,这太难了.(既然使用下划线作为成员变量的标志容易跟保留标识符冲突,那我就在变量结尾加下划线)

好了.最后,不考虑其他一些上面没有提到的设计和风格上的缺陷,我们可以得到下面的经过修正的代码:
class Complex {
public:
explicit Complex( double real, double imaginary = 0 )
: real_(real), imaginary_(imaginary) {}

Complex& operator+=( const Complex& other ) {
real_ += other.real_;
imaginary_ += other.imaginary_;
return *this;
}

Complex& operator++() {
++real_;
return *this;
}

const Complex operator++( int ) {
Complex temp = *this;
++(*this);
return temp;
}

ostream& print( ostream& os ) const {
return os << "(" << real_
<< "," << imaginary_ << ")";
}

private:
double real_, imaginary_;
friend ostream&
operator<<( ostream& os, const Complex& c );
};

const Complex operator+( const Complex& lhs,
const Complex& rhs ) {
Complex ret( lhs );
ret += rhs;
return ret;
}

ostream& operator<<( ostream& os,
const Complex& c ) {
return c.print(os);
}
-----
(结束)

 

作者Blog:http://blog.csdn.net/plpliuly/

 

 

 

 

 

 

C++ articles:Guru of the Week #3:使用标准库
出处 http://www.Iforgetit

作者:Hub Sutter
译者:plpliuly

/*此文是译者出于自娱翻译的GotW(Guru of the Week)系列文章第3篇,原文的版权是属于Hub Sutter(著名的C++专家,"Exceptional C++"的作者)。此文的翻

译没有征得原作者的同意,只供学习讨论。——译者
*/

#3 使用标准库
难度:3/10

使用标准库提供的算法比你自己手工写一个要方便的多。仍然以GotW #2中讨论的函数定义为例子,我们将看到如果直接使用标准库将会避免很多问题。

问题:
如果我们用标准库中的已有算法代替GotW#2中的最初代码中的循环,有哪些问题可以自然避免?(注意:与以前一样,不能改变函数的语义。)

GotW #2中的问题回顾:
最初的实现:
string FindAddr( list l, string name )
{
for( list ::iterator i = l.begin();
i != l.end();
i++ )
{
if( *i == name )
{
return (*i).addr;
}
}
return "";
}

经过修改后,除了l.end()依然是每次循环到要调用,其余的不足之处均已修改(译者:请参看GotW #2):
string FindAddr( const list & l,
const string& name )
{
string addr;
for( list ::const_iterator i = l.begin();
i != l.end();
++i )
{
if( (*i).name == name )
{
addr = (*i).addr;
break;
}
}
return addr;
}

答案:
在最初的代码基础上,仅仅用find()代替循环而不做任何其他的修改就可以避免两个不必要临时对象而且可以几乎把初始代码中的对l.end()的冗余调用全部

去掉:
string FindAddr( list l, string name )
{
list ::iterator i =
find( l.begin(), l.end(), name );

if( *i != l.end() )
{
return (*i).addr;
}
return "";
}
再结合我们在GotW #2中提到的修改方法,最终可以得到如下代码:
string FindAddr( const list & l,
const string& name )
{
string addr;
list ::const_iterator i =
find( l.begin(), l.end(), name );

if( i != l.end() )
{
addr = (*i).addr;
}
return addr;
}
[忠告]尽量使用标准库算法,因为那样比自己手动重新写一个算法要快捷,而且不易出错。
(结束)

 

 

作者Blog:http://blog.csdn.net/plpliuly/

 

 

 

 

 

C++ articles:Guru of the Week #2 --临时对象
出处 http://www.gotw.ac

作者:Hub Sutter
译者:plpliuly

/*此文是译者出于自娱翻译的GotW(Guru of the Week)系列文章第二篇,原文的版权是属于Hub Sutter(著名的C++专家,"Exceptional C++"的作者)。此文的翻译没有征得原作者的同意,只供学习讨论。——译者
*/

#2 临时对象
难度:5/10

不必要的临时对象常常导致代码冗余和执行效率低下。

问题:
假设你正在看一段代码,代码中有如下一个函数。这个函数中至少有3处产生了不必要的临时对象。
看看你能够找出几处,该怎样修改?

string FindAddr( list l, string name )
{
for( list ::iterator i = l.begin();
i != l.end();
i++ )
{
if( *i == name )
{
return (*i).addr;
}
}
return "";
}

答案:
不管你信不信,就在这寥寥几行代码里出现了三处明显的不必要的临时对象,两处不太明显的临时对象,还有一处值得注意的地方。

string FindAddr( list l, string name )
^^^^^^^1^^^^^^^^ ^^^^^2^^^^^
1&2.参数应该是const引用类型.上述语句中的传值参数会导致list和string的拷贝操作,这可能是很耗时的。
[准则]尽量使用const&来代替值拷贝传递参数。

for( list ::iterator i = l.begin();
i != l.end();
i++ )
^3^
3.此处比上两处稍难看出。此处如果用前自增操作代替后自增的将更有效率,因为对象的后自增操作需要对象执行自增操作并返回一个自增前原值的临时对象(译者:重载过前自增和后自增操作符的读者应该都清楚二者的区别)。注意这种情况对于象int这样的C++原始类型也是适用的。
[准则]尽量使用前自增,避免使用后自增。

if( *i == name )
^4
4.虽然我们没有看到Employee类的定义,但是要使得上面的语句有效,这个类必须定义了到string类型转换操作符或者定义了以string类型为参数的构造函数。这两种情况都会产生一个临时对象,前者调用string的等值比较操作符(operator==),后者调用了Employee的等值比较操作符。(唯一不产生临时对象的情况就是string和Employee类中重载了以对方为参数类型的等值比较操作符。)
[准则]要小心隐藏在参数转换后面产生的临时对象。一个避免产生这种临时对象的解决办法就是用explicit修饰符对构造函数加以限制。

return "";
^5
5.此处产生了一个临时的空string对象。
更好的方法是声明一个局部的string对象来存放返回值,并且最后以一条返回该对象的值的语句作为统一的返回出口。这将使得编译器可以在某些情况下采用返回值优化手段省略掉这个局部对象。比如下面的情形:调用者通过如下代码调用这个函数:
string a = FindAddr( 1, "Harold" );
[准则]遵循单个出口原则。决不要在同一个函数中存在多个返回语句。

[注意:在作了更多的性能测试以后,我并不完全赞成上述的原则。《Exception C++》中已经对此作了不同阐述。]

string FindAddr( list l, string name )
^^^*^^
*.此处是一个题外话,但很值得注意。看起来好像简单地将函数返回值类型从string类型改为引用类型sting&就又可以避免产生一个临时对象,但这是错误的!如果你幸运的话,你的程序会在函数调用者使用返回的引用时就马上崩溃,因为引用所指的局部对象已经不存在了。如果你不够幸运的话,你的程序看起来好像可以工作,但却不定期的崩溃,那将可能让你熬好几个晚上的长夜来调试找错。
[准则]千万千万不要将一个局部对象的引用作为返回值。
(注意:新闻组上有些人贴文正确的指出:可以通过声明一个静态对象,并在 没有查到对应雇员的地址时返回这个静态对象的引用,这样就可以把函数返回值改为引用类型而并不改变函数的语义。这同样也说明你在返回引用时必须了解所引用对象的生命周期以保证返回的引用有效。)

上述代码中还有一些可以优化的地方,比如可以避免调用end(),可以(或者说应该)使用一个const_iterator类型的迭代器。暂时不考虑这些,我们可以写出如下的较好的函数定义:
string FindAddr( const list & l, const string& name )
{
string addr;
for( list ::const_iterator i = l.begin();
i != l.end();
++i )
{
if( (*i).name == name )
{
addr = (*i).addr;
break;
}
}
return addr;
}
----------
(结束)

 


作者Blog:http://blog.csdn.net/plpliuly/

 

 

 

 

 

 

C++ articles:Guru of the Week #1
出处 http://www.gotw.ac

作者:Hub Sutter
译者:plpliuly

/*此文是译者出于自娱翻译的GotW(Guru of the Week)系列文章第一篇,原文的版权是属于Hub Sutter(著名的C++专家,《Exceptional C++》的作者)。此文的翻译没有征得原作者的同意,只供学习讨论。——译者
*/

#1 变量的初始化(97年2月21日提出)
难度:4/10
你知道有多少种初始化变量的方法?可千万要当心那些看起来象变量初始化,而实际上并不是的错误哟。

问题:
请看下面的语句,它们之间是否有区别?
SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;

解决方法:
我们从后往前分别讨论上面的四种情况:
SomeType t;
变量t是通过却省构造函数SomeType::SomeType()初始化.
SomeType t();
这条语句具有一点“欺骗”性,乍看起来象是一个变量声明,其实,它是一个函数声明,这个函数没有参数,返回一个SomeType类型的返回值。
SomeType t(u);
这是一个直接的初始化。变量t通过构造函数SomeType::SomeType(u)初始化。
SomeType t = u;
这是一个拷贝初始化,变量t总是通过SomeType的拷贝构造函数初始化。(尽管这儿有"=",但这只是为了和C语法的兼容——这里只是初始化,而没有赋值操作,因此操作符=不会被调用。)
从语义上讲:如果变量u为SomeType类型,那么上述语句就和"SomeType t(u)"或者调用SomeType的拷贝构造函数是等同的;如果u是其他类型的变量,那么上述语句就和"SomeType t( SomeType(u) )"是等同的——也就是说,u被转换成一个临时的SomeType对象,而t是以该临时对象拷贝构造而成。
注意:对于这种情况,具体的编译器常常被允许(但不是必需的)为了优化性能而并省去拷贝构造这一步(也就是将临时对象的内存空间直接作为t的内存空间——译者注)。如果是编译器作了这种优化的话,那拷贝构造函数本身必须仍然是可访问的。

[忠告]:尽量使用"SomeType t(u)"的形式来初始化变量。它在任何"SomeType t = u"有效的地方使用都是有效的,而且具有其它的优点(比如,它可以带多个参数)。
(结束)

 


作者Blog:http://blog.csdn.net/plpliuly/

 


堆 栈 C C++
出处

"堆"和"栈"

作者:arya

堆(heap)和栈(stack)是C/C++编程不可避免会碰到的两个基本概念。首先,这两个概念都可以在讲数据结构的书中找到,他们都是基本的数据结构,虽然栈更为简单一些。

在具体的C/C++编程框架中,这两个概念并不是并行的。对底层机器代码的研究可以揭示,栈是机器系统提供的数据结构,而堆则是C/C++函数库提供的。

具体地说,现代计算机(串行执行机制),都直接在代码底层支持栈的数据结构。这体现在,有专门的寄存器指向栈所在的地址,有专门的机器指令完成数据入栈出栈的操作。这种机制的特点是效率高,支持的数据有限,一般是整数,指针,浮点数等系统直接支持的数据类型,并不直接支持其他的数据结构。因为栈的这种特点,对栈的使用在程序中是非常频繁的。对子程序的调用就是直接利用栈完成的。机器的call指令里隐含了把返回地址推入栈,然后跳转至子程序地址的操作,而子程序中的ret指令则隐含从堆栈中弹出返回地址并跳转之的操作。C/C++中的自动变量是直接利用栈的例子,这也就是为什么当函数返回时,该函数的自动变量自动失效的原因(因为堆栈恢复了调用前的状态)。

和栈不同,堆的数据结构并不是由系统(无论是机器系统还是操作系统)支持的,而是由函数库提供的。基本的malloc/realloc/free函数维护了一套内部的堆数据结构。当程序使用这些函数去获得新的内存空间时,这套函数首先试图从内部堆中寻找可用的内存空间,如果没有可以使用的内存空间,则试图利用系统调用来动态增加程序数据段的内存大小,新分配得到的空间首先被组织进内部堆中去,然后再以适当的形式返回给调用者。当程序释放分配的内存空间时,这片内存空间被返回内部堆结构中,可能会被适当的处理(比如和其他空闲空间合并成更大的空闲空间),以更适合下一次内存分配申请。这套复杂的分配机制实际上相当于一个内存分配的缓冲池(Cache),使用这套机制有如下若干原因:

1. 系统调用可能不支持任意大小的内存分配。有些系统的系统调用只支持固定大小及其倍数的内存请求(按页分配);这样的话对于大量的小内存分类来说会造成浪费。

2. 系统调用申请内存可能是代价昂贵的。系统调用可能涉及用户态和核心态的转换。

3. 没有管理的内存分配在大量复杂内存的分配释放操作下很容易造成内存碎片。

堆和栈的对比

从以上知识可知,栈是系统提供的功能,特点是快速高效,缺点是有限制,数据不灵活;而堆是函数库提供的功能,特点是灵活方便,数据适应面广泛,但是效率有一定降低。栈是系统数据结构,对于进程/线程是唯一的;堆是函数库内部数据结构,不一定唯一。不同堆分配的内存逻辑上无法互相操作。栈空间分静态分配和动态分配两种。静态分配是编译器完成的,比如自动变量(auto)的分配。动态分配由alloca函数完成。栈的动态分配无需释放(是自动的),也就没有释放函数。为可移植的程序起见,栈的动态分配操作是不被鼓励的!堆空间的分配总是动态的,虽然程序结束时所有的数据空间都会被释放回系统,但是精确的申请内存/释放内存匹配是良好程序的基本要素。

 

作者Blog:http://blog.csdn.net/arya/

 


初学者看过来:简单谈谈 C/C++ 递归的思想,实现,以及和循环的关系。
出处

很多初学者往往对递归迷惑不解,也在这上面花了不少的时间。其实教材上的例子很经典,只是它说的有一些唠叨了。初学者会看的头大的。编程是解决问题的,而现实中很多的问题都是比较简单的,没有象汉诺塔那么复杂。我们也不必追究递归到底是怎样实现的,我们只是要会用递归,会用递归来为我们解决一些问题,这就行了。

首先来看一个例子:

有一个Febonacci序列:

1,1,2,3,5,8,13,,21,34........

它的问题是:求这个序列中的第N个数。

由于它的函数原形是:f(n)=f(n-1)+f(n-2)

这用递归很容易就可以写出代码来,一点都不费事:

int Febc(int n) {

if(n<3) return (1);

else

return (Febc(n-1)+Febc(n-2));

}

噢~~~~~也许你会说递归真是太简单了,简直就是一个数学模型嘛,呵呵。

其实,递归函数的工作过程就是自己调用自己。有一些问题用递归就很容易解决,简单的你自己都会吃惊。

我们做事情,一般都是从头开始的,而递归却是从末尾开始的。比如上面的函数吧,当n>3时,它显然只能求助于n-1,n-2。而(n-1)>2,(n-2)>2时,它们就求助于:(n-1)-1,(n-1)-2;(n-2)-1,(n-2)-2;然后··············直到(n-k)<3,(n-k-1)<3时,函数Febc终于有了返回值1 了,它再从头开始计算,然后一直算到n 为止。

通过上面的例子,我们知道递归一定要有一个停止的条件,否则递归就不知道停止了。在上面的例子中, if(n<3) return (1); 就是停止的条件。

然而,使用递归的代价是十分巨大的:它会消耗大量的内存!!递归循环时它用的是堆栈,而堆栈的资源是十分有限的。上面的例子你只能用一个很小的n值。如果n=20,即Febc(20)的话,它将调用Febc(n)函数10000多次!!!而上面一个例子用循环也是十分容易写的:

/*using turboc2*/
int febc(int);
main()
{
int n;
scanf("%d",&n);
febc(n);
}

int febc(int n)
{
int a[3],i;
a[0]=a[1]=a[2]=1;
for(i=3;i<=n;i++)
a[i%3]=a[(i+1)%3]+a[(i+2)%3]; /*实现 Febc(i)=Febc(i-1)+Febc(i-2)*/
printf("/n%d/n",a[n%3]);
}

有兴趣者不妨输入一个较大的n值,然后比较比较这二个函数计算的速度。当然, 如果你使用的n太大的话,递归可能发生错误。如果死机了可别骂我哦~~~ 我已经提醒过你了 :)

现在我们再来看看一个求从1 加到100的循环:

/*turboc2*/

main()

{ int i,n;

for(i=1;i<101;i++)

n+=i; }

这很简单没什么可说的。 但是,你能不能写出相应的递归函数呢?

下面就是递归(请注意了,这种做法不推荐!! 我只是为了说明问题才这么写的。)

/*using Turboc2*/

int a=0;
int account(int);
main()
{
account(100);
printf("%d",a);
}
int account(int i)
{
if(i==0) return 0; /*停止条件*/
else
a+=account(i-1)+1; /*实现递归*/
}

在C/C++的问题中,我曾经回答过这样的一个问题:

若一头小母牛,从出生起第四个年头开始每年生一头母牛,按此规律,第n年时有多少头母牛? 请问如何用递归涵数求此问题?

先写出函数表达式:f(n)=f(n-1)+f(n-3)

为什么f(n)=f(n-1)+f(n-3)呢,请看:
f(n)-f(n-1)=f(n-3)
因为第n年要比n-1年多的牛,都是大于三岁的牛生的小牛,而f(n-3)正是那些在n年大于三岁的牛,然后它们在第n年生下相同数量的小牛。(请用BorlandC++3.1或其他C++编译器)

#include
#include

int cattle(int,int);

void main()
{
int ct,n;
cout<<"Please input the original cattle number:"< cin>>ct;
cout<<"Input how many years it past:"< cin>>n;
cout<<"You have "< getch();
}

int cattle(int ct,int n)
{
if(n<4) return (ct); /*停止条件*/
else
return (cattle(ct,n-1)+cattle(ct,n-3)); /*实现递归*/
}


怎么样,很简单吧。 会用循环求解吗?

递归在实际的编程中并不常用,但是在某些情况下,它是非常强有力而漂亮的工具。掌握它的原理时会十分有用的。

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值