从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:使用标准库
作者: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 --临时对象
作者: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
作者: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)); /*实现递归*/
}
怎么样,很简单吧。 会用循环求解吗?
递归在实际的编程中并不常用,但是在某些情况下,它是非常强有力而漂亮的工具。掌握它的原理时会十分有用的。
注c是面向过程的,而C++是面向对象的
C++和VC++区别
C++ 是一种语言
VC++是开发环境,是可视化的它用C++,到了C++这个语言
比方说你拼积木,你当然可以自己做积木,然后搭房子。这是C++
当然你也可以用做好的积木,直接去搭房子这是VC++,所谓的积木就是MFC等frame
但是这个时候积木往往不是你所想要的
你要自己做,这个就是用C++做底层开发
c#是C++的升级版本,也是计算机语言
如果你只是想做快速开发当然VC是不错的工具
java与c++区别
1 多继承:Java中不使用多继承,而是用一个叫“Interface”的结构,Java中的接口与C++中的一个只有纯虚函数的类等价。但Java接口不是一个类。接口中声明的方法不能在接口中执行,而且一个Java接口不能有任何成员变量。所以对接口的多继承就不会导致被加到C++中的虚继承的问题。所以在Java中不需要虚继承,因为它不可能在多于一个路径中继承到相同的成员变量。Java中使用类聚合来替换多继承,特别是在Observer模式中,是一致的。
2 内存管理:Java使用垃圾收集机制,垃圾收集是一个内存管理scheme ,它在对内存块的所有引用都不存在后就自动的释放这个内存块。垃圾收集机制使得对特定种类的应用很容易编程,程序的设计者不需要考虑清除“dead”内存。C++中缺少这种机制,很多人为C++提供了garbage collectors ,有的是第三方软件,有的是在网上的共享软件。这些collectors很不完美,但方便了使用。
对于Java没有方法来使writer来手动的发现去管理内存。很明显,你不能写你自己的内存管理者,也不能在内存管理者控制的内存中构造对象。
原因:任何内存管理计划都允许程序有指针和对未使用空间的引用,这是违反特定的安全的。手动管理内存的任何形式,如holding on to dead pointers or references ,都会导致安全漏洞。在典型的Java环境中,安全是一个涉及到的严重的问题。Java applets经常在web浏览器中运行和被下载。用户可能不知道运行的applet,因为他们的浏览器是活动的。如果允许手动内存管理,不道德的人就可能发布包含不安全applet的web页。这些applet可能就被正在浏览的,没有防备的用户下载到了自己的系统中。一但下载了这些applet,就可以把私有信息返回给web页的作者。
在Java中缺少手动内存管理的问题?这使得Java在硬实时(a hard real time system)约束的系统中使用困难。当garbage collector运行时,这个问题是很难预测的。可以使用有意义的CPU时间来处理,必须建立一种方式,使得你要用的内存可靠而不会引起垃圾收集。在实时应用中使用任何Java库都可能导致垃圾收集,要注意。
3. Finalize
Java 中的finalize 方法大概对应于C++中的析构器。当一个对象被garbage collector收集了,它的finalize方法就被调用了。注意,在大多数情况下,finalize不是一个释放由对象持有的资源的好的位置。可能需要很长时间这个对象才好被garbage collector收集,这样,在finalize中它们释放的资源可能被持有很长时间。
导出类的finalize方法必须明确调用基类的finalize方法。如果忘记这样做,基类中的finalize方法就完全不会被调用。
4. ToString()
任何有toString方法的类,都是被用于特殊的上下文中,希望得到一个String 。在一个String的上下文中,对象的toString方法会自动的调用。对toString()方法的自动使用好像是自动转换C++系统的不成熟版本。这个特征使得String类有些地方比其它类特殊。我认为,对Java设计者,可以看作是使用一个一般的转换系统(如,一个方法模板)
5. Exceptions and finally
在C++中,当一个异常离开了一个方法的范围,被定位在堆栈的所有对象被回收,它们的析构器被调用。这样,如果你想释放一个资源或当一个异常发生时,清除什么。你必须把代码放到一个被定位到堆栈上的对象的析构器中。
这是人为的,错误倾向的,不方便的。而且从构造器和析构器中抛出异常是有问题的,这在C++中是难使用的问题。
在Java中,每个try块可以有一个finally语句,这样一个块退出的任何时间,不管退出的原因(try块执行结束或者异常抛出),finally语句中的代码都被执行。
这看起来比C++的机制要好,可以在finally语句中直接清除代码,而不是人为的把它们放到析构器中。而且,被清除的代码可以与被清楚的变量保持在相同的范围中。主要的不利方面是强迫应用程序员(1)知道被定位在块中的每个资源的释放协议(2)在finally块中要明确的处理所有的清除操作。
6. Threads
Java中线程的执行是最小的和优雅的。方法可以被从并发修改中保护的简单方式是简单的标志和严格的代码机制,在两个线程间创建一个集合点是非常简单的,所有的结合也是一个好的语言特征。
7. Operator Overloading
在Java 1 中不能像C++那样使用操作符重载。
8. Templates
模板是C++中好的特征。在Java中不能创建一个类型安全的容器。Java中所有的容器都能包含任何种类的对象,这会导致问题。
在Java中所有的casts(造型)是类型安全的,这减轻了这个问题。即Java中的造型等价于C++中引用的dynamic_cast (动态造型)。不正确的造型结果在一个异常中被抛出。因为一个Java容器中的对象必须被下溯(downcast),且因为这样的造型相对的安全,类型安全容器的需要就减轻了。
类型安全容器并不是模板的唯一好处。C++中的模板可以获得静态多态。尽管在C++和Java中可以使用抽象基类来获得这种多态,使用模板还是用独特的好处的,如,不需要virtual overhead ,如,不需要额外的时间和内存来管理对正常C++虚函数的动态绑定。
9. Break和Continue标签
对结构化语言的原则是不允许使用goto语句的,但也不允许使用破坏单入口,单出口的范例(paradigm)。如使用goto语句来创建for循环或者while循环的等价体并不违反结构化编程。
单入口,单出口范例要求对每个代码块只能有一个入口点和一个出口点。不能有其它方式从代码块中间进入或者退出。入口在顶部,出口在底部。
在C,C++中break和continue的使用,或者Java中的Continue的使用都违反了单入口,单出口的范例。我们使用它们在一个循环的中间来转换控制退出。事实上,对一下封装块来说,它们不知道它们正被退出,且可能写了假设它们不被退出。这会导致很难识别的错误。
简单的C/C++程序实例作者:vich 发布于 2008-10-8 22:44 评论(0)有2266人阅读
下面通过2个例子(分别用C及C++编写)的讲解,来对C程序和C++程序有个初步的感性认识,并分析程序特点,找出C及C++两者的基本区别。
【例1.1】由键盘输入2个整数,计算他们的商和余数,并在屏幕上输出。
程序设计如下:
main()
{
int a,b,c,d; /*变量说明*/
printf("/n input a,b=?/n"); /*屏幕输出提示语*/
scanf("%d %d",&a,&b); /*由键盘输入a,b的值*/
c=a/b; /*求a除以b的商,赋予c*/
d=a%b; /*求a除以b的余数,赋予d*/
printf("/n %d / %d =%d /n %d %% %d=%d/n",a,b,c,a,b,d); /*输出*/
}
本程序中仅由一个函数模块组成。第一行main()表示这是主函数,一对大括号{}括起来的是函数体。函数体所描述的内容的是:定义整型变量a,b,c,d,然后屏幕上输出“input a,b=?”提示语,意思是提醒输出a,b两个数,接着用调用scanf函数,从键盘输入两个整数,存入变量a和b的存储单元中,再计算a与相除的商(存于c中)和余数(存于d中),最后调用printf语句输出两个算术的运算式。
程序中的/*……*/代表注释,是个编写程序者和阅读程序者看的,它不是程序的组成部分,不影响程序的编译和运行,仅起注释作用。至于程序中的某些细节,例如“%d”、“/n”等内容,我们暂且不去讨论它,以后会学习的。
程序运行后屏幕输出:
input a,b=?11 4 ↓回车(键盘输入两书为11和4)
11/4=2
11%4=3
下面用C++改写该程序:
#include <iostream.h> //I/O流库头文件
void main()
{
int a,b;
cout<<"/n input a,b=?/n"; //输出提示语
cin>>a>>b; //对a、b变量输入
int c; //变量可随用随定义
c=a/b;
int d=a%b;
cout<<"/n a/b="<<c<<end; //输出a除以b的商
cout<<"/n a%b="<<d<<end; //输出a除以b的余数
}
将上面的两个程序作比较,显然C++程序与C程序有三点明显的不同之处:
其一,程序的注释改变为简化的标记形式“//”(针对单行注释);
其二,数据的输入输出改用cin和cout两个标准流设备,而且不用考虑数据类型,其中cin用于键盘输入,cout用于显示器输出;
其三,输入输出流cin和cout未被默认,因此程序首部必须写上编译预处理命令#include<iostream.h>。
【例 1.2】由键盘输入圆柱底面的半径和高,计算圆柱体的体积。
程序设计如下:
float volm(r,h) /*定义volm函数,用于计算圆柱体的体积*/
float r,h
{
float x;
x=3.14159*r*r*h;
return x;
} /*volm函数结束*/
main() /*定义主函数*/
{
float radius,height,volume;
scanf("%f%f",&radius,&height);
vllume=volume(radius,height);
printf("volume of cylinder is:%f/n",volume);
}
本例包含2个函数,一个是mail()主函数,另一个是volm()辅函数。volm函数有两个参数r(底面半径)和h(柱体高),函数的功能是对依据r和h计算圆柱体的体积,再通过return语句将结果返回给主函数。main函数首先对实型变量radius和height输入半径和高,然后调用volm函数计算圆柱体的体积,并在屏幕上输出计算结果。
下面用C++改写该程序:
#include <iostream.h> //I/O流库头文件
/*类的声明部分*/
class Cylinder //圆柱类
{
public: //定义公有成员函数或数据成员
Cylinder(float r,float h); //带两个参数的构造函数
float GetVolume()const; //计算圆柱体积的成员函数
private: //定义私有成员函数或数据成员
float rd; //圆柱底半径
float hd; //圆柱高
};
/*类的实现部分*/
Cylinder::Cylinder(float r,float h) //定义带两个参数的构造函数并完成初始化
{
rd=r;hd=h;
}
float Cylinder::GetVloume()const //定义成员函数,计算圆柱体体积
{
return 3.141519*rd*hd;
}
int main() //定义测试程序(即主函数)
{
Cylinder myCylinder(2,3); //定义对象并初始化
cout<<"The Volume is :"<<myCylinder.GetVolume(); //调用函数并输出结果
}
本程序运行后,屏幕输出类容为:The Vloume is :37.3991。
与上面的C程序相比,本程序也仅有一个主函数,并且是通过在主函数中调用其他函数实现圆柱体体积的计算。但两者的去别是:C程序中,对数据r、h以及计算体积的操作没有进行封装,是分散的;而C++程序把与圆柱相关数据(r和h)以及操作(计算并返回体积值)集中起来形成一个类,使用户通过创建对象来控制这个圆柱,这样只有对象中的函数可以改变数据,避免给对象内部的信息实施不恰当的操作。以后会对类和对象进行详细介绍。