九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

别让习惯害了你,注意size_t变量作循环条件时,逆序是不可行的!

欢迎转载,但请标明作者 “九天雁翎”,当然,你给出这个帖子的链接更好。

在自己编写求自然数内n的n!时碰到的问题。

#include <iostream>
#include <iomanip>
#include <cmath>
#include <cstdlib>
using namespace std;

size_t getbit(const size_t &aN);
void init(unsigned char *p,size_t &am);
void compute(unsigned char *p,const size_t &an);
void print(unsigned char *p,const size_t &am);
int main()
{
 cout <<"Input the /"n/" you want to compute:";
 size_t n;
 cin >> n;
 if(n == 0)           //检验输入
 {
  cerr<<"Please input a integer more than zero."<<endl;
 }
 size_t m = getbit(n);            
 unsigned char *pn = new unsigned char[m];
 if(!pn)            //检验空间分配
 {
  cerr<<"The factor is too big."<<endl;
 }
 init(pn,m);        //初始化
 compute(pn,n);   //计算n!
 print(pn,m);         //输出
 delete []pn;
 return 0;
}
//-----------------------------------------------------------
size_t getbit(const size_t &aN)
{
 double sum = 1.0;
 for(size_t i = 1;i<=aN;++i)
 {
  sum+=log10(double(i));
 }
 return size_t(sum);
}
//------------------------------------------------------------
void init(unsigned char *p,size_t &am)
{
 p[0]=1;
 for(size_t i = 1;i != am;++i)
 {
  p[i] = 0;
 }
}
//------------------------------------------------------------
void compute(unsigned char *p,const size_t &an)
{
 double bitcount = 1.1;
 size_t begin = 0;
 for(size_t i = 2;i<=an;++i)
 {
  size_t and = 0;
  bitcount +=log10(double(i));
  if(p[begin]==0)
   ++begin;
  for(size_t j = begin ; j < size_t(bitcount);++j)
  {
   and += i * p[j];
   p[j] = unsigned char(and % 10);
   and /= 10;
  }
 }
}
//----------------------------------------------------------------
void print(unsigned char *p,const size_t &am)
{
 size_t bit = 0;
 for(size_t i = am - 1;i >= 0;--i)
 {
  if(bit % 50 == 0)
   cout <<endl<<"第"
   <<setw(3)<<(bit/50+1)<<"个50位:";
  cout << size_t(p[i]);
  ++bit;
 }
}
首先声明的是,我是在学习用数组处理这个问题,所以没有用vector,而是动态分配了数组,求位数用的是(n!=10^M)估算算法,每一位只保存结果的一位,为了节省空间用unsigned char保存一位数,在在vc2005下编译通过了,不过运行却得不出结果,还有假如在最后把输出的for(size_t i = am;i >= 0;--i)改为
for(int i = am;i >= 0;--i),结果会好一点,但是还是得不出正确结果,正准备找高手说明下问题。还有用size_t为什么似乎会导致这里的死循环,而int不会?
最后我发现,把print()定义成这个样子,反而又可以。所以比较肯定的说是输出函数的问题,但是我实在不知道是什么问题。
void print(unsigned char *p,const size_t &am)
{
 cout <<size_t(p[2])<<size_t(p[1])<<size_t(p[0])<<endl;
}

最后我老实编了一个正常简单的逆序循环发现也不可以,我刚开始甚至还怀疑是不是size_t就没有正常的“--”自减运算符,然后编小程序证明不是,而且在循环条件〉=3,2,1时都没有问题,就到了大于0就不行了,我这时才突然醒悟过来,其实size_t 就是unsigned int啊,一个没有符号的整数在C++里面就意味着一个永远为正的数,我的行为就像是等一个数的绝对值,一个数开2次方根,一个数的平方去小于零一样可笑!呵呵,大家可不要犯我一样的错误啊。然后我把print()改成下面这个样子后,果然一切都正常了。

void print(unsigned char *p,const size_t &am)
{
  size_t bit = 0;
 for(size_t i = am;i != 0;--i)
 {
  if(bit % 50 == 0)
   cout <<endl<<"第"<<setw(3)<<(bit/50+1)<<"个50位:";
  cout << size_t(p[i-1]);
  ++bit;
 }
 cout<<endl;
}

 

阅读全文....

警惕!C++里面“=”不一定就是等于(赋值)。

欢迎转载,但请标明作者 “九天雁翎”,当然,你给出这个帖子的链接更好。

让我们来现在看一个这样的程序:

#include
using namespace std;
class HasPtr
{
public:
int *ptr;
int val;
HasPtr(const int &p,int i):ptr(new int(p)),val(i) { }
HasPtr& operator=(const HasPtr &rhs)
{
 ptr = new int;

*ptr = *rhs.ptr;
val =rhs.val;
return *this;
}
~HasPtr()
{
delete ptr;
}
};

int main()
{
int ival = 5;
HasPtr a(ival,5);
HasPtr b = a;
cout<<*(a.ptr);
return 0;
}

这是看起来是一个没有任何问题的程序,并且在指针的回收处理上非常好,用的是值型指针来处理类里面的指针,在VC(以后都是指VC++.net 2005)中编译也可以通过,在Dev-C++4.9.9.0 中编译运行都没有问题。但是在vc中运行却会出问题。原因在哪里?经我论坛发帖求教,是因为HasPtr b = a; 语句其实并不是赋值,而是调用了构造函数。不信?证明如下:

#include <iostream>
using namespace std;
class HasPtr
{
public:
 int *ptr;
 int val;
 HasPtr(const int &p,int i):ptr(new int(p)),val(i) { }
 HasPtr(const HasPtr &orig):ptr(new int(*orig.ptr)),val(orig.val)
 {
 cout<<"Use me(copy constructor)"<<endl;
 }
 HasPtr& operator=(const HasPtr &rhs)
 {
  cout <<"Use me(=)"<<endl; 
  *ptr = *rhs.ptr;
  val =rhs.val;
  return *this;
 }
 ~HasPtr()
 {
  delete ptr;
 }
};

int main()
{
 int ivala = 5;
 HasPtr a(ivala,5);
 HasPtr b = a;
 ivala = 6;
 cout<<*(a.ptr)<<*(b.ptr)<<endl;
 return 0;
}

这一点在VC和在dev-c++中都是一样的。你会发现调用的都是copy constructor(复制构造函数),不过据说之所以在dev-c++中没有出错,是因为可怜的dev-c++检测能力太差。。。。。。。。。。。

阅读全文....

浅谈C++类(7)--析构函数

欢迎转载,但请标明作者 “九天雁翎”,当然,你给出这个帖子的链接更好。

 不知不觉我都写了6讲了,的确这样讲出来的学习才能迫使我真的去调试每个书上出现的代码,去想些自己能讲出什么新的书上没有的东西,这才是真的学习吧,以前看完书,做道题式的就以为自己基本都掌握了,在类这里好像行不通,因为我的C基础不适合这里。。。。呵呵不说题外话了。这次讲析构函数,相对于构造函数。析构函数就是在类的声明周期结束的时候运行的函数,一般用来把一个类的资源释放出来的家伙。就我了解,JAVA好像不需要这样的工作,他们有垃圾回收器,我看一个比较理性的程序员评价这种情况是有利有弊,类似的简化让JAVA成为了最佳商业模式开发软件,但是让JAVA程序员太脱离底层,让他们的知识匮乏,因为编JAVA不需要那么多知识,而且让JAVA失去了很多底层应用。另外这样的垃圾回收是耗资源的,当时Bjarne Strooustrup就考虑过也给C++加一个这样的特性,但他又说,作为一个系统开发级及常用来开发驱动程序的语言,他无法接受那样的效率损失。所以最后C++没有加上这个特性。又讲多了,看析构函数吧。

例7.0:

#include <string>
#include <iostream>
using namespace std;
bool being = true;                 //定义一个全局bool变量
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
public:
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst = "apple",const string &cst = "green"):name(nst),colour(cst)
 {
 being = true;                          //表示这个东西存在
 }  //构造函数
 ~Fruit()                        //这就是传说中的析构函数
 {
 being = false;                      //表示他不存在了
 } 
};
int main()
{
 {
  Fruit apple("apple");  //定义一个Fruit类对象apple
  cout <<"apple being?: "<<being<<endl;             
 }
 cout <<"apple being?: "<<being<<endl;
 return 0;
}

首先看到不要惊讶 :),我们的构造函数和析构函数都作了些什么啊。我说过构造函数就是构造一个类对象会运行的函数,析构函数就是类生命周期结束时运行时运行的函数,不仅仅是我们的一般理解啊,从逻辑上来讲,他们可以DO Everything,你首先要知道他们能干什么啊:)而且还要知道他们什么时候起作用,因为我们用一个大括号把apple的定义括起来了,在大括号消失的时候,apple就需要消失了,于是这时候调用了析构函数。下面我们先看看可以做什么的例子。

例7.1:

#include <string>
#include <iostream>
using namespace std;
bool being = true;
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
public:
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst = "apple",const string &cst = "green"):name(nst),colour(cst)
 {
 being = true;
 cout <<"Aha,I'm "<<name<<". I have created!"<<endl;
 }  //构造函数
 ~Fruit()
 {
 being = false;
 cout <<"Dame it!"<<"I'm "<<name<<". And who killed me?"<<endl;
 } 
};
int main()
{
 {
  Fruit apple("apple");  //定义一个Fruit类对象apple
  cout <<"apple being?: "<<being<<endl;
 }
 cout <<"apple being?: "<<being<<endl;
 return 0;
}

你运行看看,就知道了:)在一个对象定义的时候他会高呼自己被创造了,当他消失的时候他会宣布自己的死亡:)好的,Fruit的对象看起来已经知道自己什么时候有生命了,让我们来看看到底什么时候吧。

例7.2:

#include <string>
#include <iostream>
using namespace std;
bool being = true;
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
public:
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst = "apple",const string &cst = "green"):name(nst),colour(cst)
 {
 being = true;
 cout <<"Aha,I'm "<<name<<". I have created!"<<endl;
 }  //构造函数
 ~Fruit()
 {
 being = false;
 cout <<"Dame it!"<<"I'm "<<name<<". And who killed me?"<<endl;
 } 
};
Fruit banana("banana");
void Fb()
{
 cout<<"我是一个函数的开始"<<endl;
 Fruit pear("pear");
 cout<<"我是一个函数的结束"<<endl;
}

int main()
{
 cout<<"我是程序的开始"<<endl;
 Fb();
 cout<<"我是for循环的开始"<<endl;
 for(bool bi = true;bi;bi=false)
 {
 Fruit orange("orange");
 cout<<"我是for循环的结束"<<endl;
 }
 {
  cout<<"我是语句块的开始"<<endl;
  Fruit apple;  //第一种情况,语句块中创建。
  cout<<"我是语句块的结束"<<endl;
 }
 cout<<"我是程序的结束"<<endl;
 return 0;
}

就这个程序运行的情况来看,一个类的生命周期和一个普通变量的生命周期类似,全局变量最先创建,程序结束时结束,函数体内的变量调用时创建,函数结束时结束,for循环内的变量在for循环结束时结束,语句块内的变量在语句块结束时结束。本来Bjarne stroustrup就宣称他希望让类就像内置类型一样使用,看来他不是说着好玩的:)这里要说明的是,即使你没有定义析构函数,系统也会像定义默认构造函数一样帮你定义一个。让我们看看什么时候需要析构函数呢?

例7.3:

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
public:
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst = "apple",const string &cst = "green"):name(nst),colour(cst)
 {
 cout <<"Aha,I'm "<<name<<". I have created!"<<endl;
 }  //构造函数
 Fruit(Fruit &aF)           //还记得我吗?我是复制构造函数
 {
  name = "another " +aF.name;             
 }
 ~Fruit()
 {
 cout <<"Dame it!"<<"I'm "<<name<<". And who killed me?"<<endl;
 } 
};

int main()
{
 cout<<"main begin"<<endl;
 cout<<"created *P"<<endl; 
 {
  Fruit *p = new Fruit; 
  cout<<"created another apple"<<endl;
  Fruit apple(*p);
 }

 
 cout<<"main end"<<endl;
 return 0;
}

运行这个程序你发现什么了?对,首先,运行复制构造函数就不运行构造函数了,因为another apple没有宣布自己的诞生,其次,当语句块消失的时候another apple自动调用了析构函数,他宣布他“死”了,但是动态创建的由*p指向的对象虽然宣布自己诞生了,但是却重来没有宣布自己死过,哪怕程序结束了也是这样!!不知道vc有没有回收内存的措施,不然我甚至怀疑你要是重复调试这个程序,可以使你的机子崩溃,当然,假如可以的话将不知道需要多少次,但是理论上确实可以的。这就是内存泄露!作为一个C++程序员,需要了解的东西比一个JAVA程序员要多的多,回报是你能做的事情也多地多!这就是你需要记住的一个,动态创建的对象,记得手动把它撤销。就像下面的例子一样。

例7.4:

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
public:
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst = "apple",const string &cst = "green"):name(nst),colour(cst)
 {
 cout <<"Aha,I'm "<<name<<". I have created!"<<endl;
 }  //构造函数
 Fruit(Fruit &aF)           //还记得我吗?我是复制构造函数
 {
  name = "another " +aF.name;             
 }
 ~Fruit()
 {
 cout <<"Dame it!"<<"I'm "<<name<<". And who killed me?"<<endl;
 } 
};

int main()
{
 cout<<"main begin"<<endl;
 cout<<"created *P"<<endl; 
 {
        Fruit *p = new Fruit; 
  cout<<"created another apple"<<endl;
  Fruit apple(*p);
  cout<<"delete p"<<endl;
  delete p;
 }

 
 cout<<"main end"<<endl;
 return 0;
}

这样才能保证你的机子不会崩溃。当你删除指针的时候系统帮你自动调用了对象的析构函数,假如上面的例子还不能摧毁你对自己自己内存足够大的信心的话,看下面的例子;

例7.5:

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
public:
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst = "apple",const string &cst = "green"):name(nst),colour(cst)
 {
 cout <<"Aha,I'm "<<name<<". I have created!"<<endl;
 }  //构造函数
 Fruit(Fruit &aF)           //还记得我吗?我是复制构造函数
 {
  name = "another " +aF.name;             
 }
 ~Fruit()
 {
 cout <<"Dame it!"<<"I'm "<<name<<". And who killed me?"<<endl;
 } 
};

int main()
{
 cout<<"main begin"<<endl;
 cout<<"created *P"<<endl; 
 {
        Fruit *p = new Fruit[10]; 
  cout<<"created another apple"<<endl;
  Fruit apple(*p);
  cout<<"delete p"<<endl;
  delete []p;
 }

 
 cout<<"main end"<<endl;
 return 0;
}

你会发现创建一个对象的数组时,分别为每一个调用了构造函数,删除一个动态数组对象的时候系统帮你自动为每一个调用了析构函数,还不了赖嘛,但是别忘了p前面的[]表示这是个数组,更别忘了删除它,你可以把10改成更大的数并不删除它来尝试一下,呵呵。析构函数就讲到这里罗。

阅读全文....

浅谈C++类(6)--复制构造函数

欢迎转载,但请标明作者 “九天雁翎”,当然,你给出这个帖子的链接更好。

还记得(1)中讲到的构造函数吗?复习一下,这次我们重载一个新的默认构造函数--即当你不给出初始值时调用的构造函数,我记得我讲过这个概念吧,有吗?看下面的例子。

例6.0

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
            
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
public: 
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst,const string &cst = "green"):name(nst),colour(cst)  //构造函数
 {
 name +="s";
 }
 Fruit(istream &is = cin)   //新的构造函数
 {
  is>>colour>>name;
 }
};
int main()
{
 Fruit apple("apple");  //定义一个Fruit类对象apple
 Fruit apple2;
 apple.print();
 apple2.print();
  
 return 0;
}

发现我重载的默认构造函数没有?这次利用的是默认形参(istream &is =cin),学过io的就应该知道,他的意思表示,默认就是从标准设备输入(如键盘)。你运行下,就知道怎么回事了。现在我们讲一个新内容,复制构造函数,什么意思?先看下面的例子。

例6.1:

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
            
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
public: 
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst,const string &cst = "green"):name(nst),colour(cst)  //构造函数
 {
 name +="s";
 }
 Fruit(){}
};
int main()
{
 Fruit apple("apple");  //定义一个Fruit类对象apple
 Fruit apple2(apple);//发现这里有什么问题没有?
 apple.print();
 apple2.print();
    return 0;
}

你会发现apple2也输出了green apples,为什么啊?(apple)和("apple")一样?你这这样理解可就错了,肯定不一样嘛。但是当我们使用Fruit apple2(apple);的时候调用了哪个构造函数呢?我们没有定义一个类似的构造函数啊?按道理应该编译失败,不是吗?恩,这里调用的构造函数就叫做复制构造函数,即用一个同样类型的对象构造另一个对象的构造函数,不过在这里,我们没有定义,所以由系统帮我们自动定义的,叫做默认复制构造函数。效果自然就是复制一下。你把第一个对象改成apple3你就会发现,apple2没有办法定义了,因为它调用的是复制Fruit对象apple的构造函数,而不是用字符串"apple"那个构造函数。C++ Primer这样定义复制构造函数,我引用一下“只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰)”。我们来看看系统合成的默认复制构造函数的一个有趣应用:

例6.2:

#include <iostream>
using namespace std;
class Aint
{
public:
 int aival[3];
};
int main()
{
 Aint as={1,2,3};
 cout<<as.aival[0]<<as.aival[1]<<as.aival[2]<<endl;
 Aint bs(as);
 cout<<bs.aival[0]<<bs.aival[1]<<bs.aival[2]<<endl;
 return 0;
}

很简单的例子吧,不过也很有趣,我们都知道,数组是没有办法通过等于来复制的,要复制只能利用循环遍历,我们自己定义了一个只包含整形数组的类,而当我们利用系统合成的默认复制构造函数的时候实现了数组的复制,注意,是一次性等于复制。呵呵。这也说明了一个问题,就是系统的默认复制构造函数在对付数组时,帮我们遍历复制了。现在我们自己定义一个复制构造函数。要说明的是,一般情况下系统定义的复制构造函数已经够用了,当你自己要定义的时候是想实现不同的功能,比如更好的处理指针的复制等,下面的例子只是看看用法,我也只讲用法而不讲究有没有实际意义。

例6.3:

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
public:
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst = "apple",const string &cst = "green"):name(nst),colour(cst){}  //构造函数
 Fruit(Fruit &aF):name(aF.name),colour(aF.colour)            //这是我们自己定义的复制构造函数
 {
  name +="s";                //让他和默认的不同
 }
};

int main()
{
 Fruit apple;  //定义一个Fruit类对象apple
 Fruit apple2(apple);//调用的是我们自己定义的复制构造函数

 apple.print();
 apple2.print();          //你会发现输出多了个's'
 return 0;

}

这里你会看到我们自己定义的复制构造函数的作用,直观的看到apple只输出green apple,而apple2输出green aples,要说明的是,这也是复制构造函数也是构造函数,也可以用初始化列表,而且在C++ Primer中还推荐你使用初始化列表。下面我们看看,假如你向让你的类禁止复制怎么办啊?很简单,让你的复制构造函数跑到private里面去,这时候友元和成员还可以使用复制,那你就光声明一个复制构造函数,但是,你不定义它,在C++里面,光声明不定义一个成员函数是合法的,但是,使用的话就会导致编译失败了,(普通函数也是这样)通过这种手段,你就能禁止一切复制的发生了(其实是发现一切本需要复制构造函数的地方了)。见下例。

例6.4:

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
public:
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst = "apple",const string &cst = "green"):name(nst),colour(cst)
 {}  //构造函数
private:
 Fruit(Fruit &aF);        //把它定义在private下
};
int main()
{
 Fruit apple("apple");  //定义一个Fruit类对象apple
// Fruit apple2(apple);   //你这样的尝试会导致编译失败的,cannot access private 错误  
 apple.print();
 return 0;
}

在犯了一个我半天也没有发现的错误的后,我发现了,当利用形如Fruit apple2 = apple方式来定义并初始化一个对象的时候,调用的也是复制构造函数,详情请见那个帖子《警惕!C++里面“=”不一定就是等于(赋值)。

阅读全文....

浅谈C++类(5)--友元

欢迎转载,但请标明作者 “九天雁翎”,当然,你给出这个帖子的链接更好。

呵呵,又来了,自从我开始尝试描述类以来,我发现我自己是开始真的了解类了,虽然还不到就明白什么叫oo的高深境界,起码对于类的使用方法了解的更多了,希望你看了以后也能有所进步啊:)

现在开始讲一个有利有弊的东西,友元(friend),我以前讲过了private的数据和函数别人是不能直接调用的,这一点对于封装起到了很重要的作用。但是有的时候总是有调用一个类private成员这样需要的,那怎么办呢?C++给了我们友元这个家伙,按我的习惯,首先看个例子。当然,还是我们的水果类:)

例5.1:


 #include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
 friend bool isSame(Fruit &,Fruit &);   //在这里声明friend友元函数
public:
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst = "apple",const string &cst = "green"):name(nst),colour(cst){}  //构造函数
 
 Fruit(){}
};
bool isSame(Fruit &aF,Fruit &bF)
{
 return aF.name == bF.name;        //注意,这个函数调用了Fruit的private数据,本来可是不允许的.
}
int main()
{
 Fruit apple;
 Fruit apple2(apple);
 Fruit orange("orange","yellow");
 cout<<"apple = orange ?: "<<isSame(apple,orange)<<endl; 

 cout<<"apple = apple2 ?: "<<isSame(apple,apple2)<<endl;
 
    return 0;
}

 这里,我们声明了一个isSame()检测是否同名的函数,而且这不是Fruit类的一个函数,虽然他在类里面声明了,怎么看出来?假如是类的成员函数,在外部定义必须要Fruit::这样定义,不是吗?isSame()没有这样,他是个独立的函数,但是他可以调用Fruit类的私有成员,因为在类里声明了他是Friend的,这就像你告诉保安(编译器)某某(isSame)是你的朋友(friend),然后让他可以进入你的家(调用私有成员)一样,别人就不允许(非友元函数不允许),这样说,够明白吗?你可以尝试去掉friend声明看看编译错误。证明friend的作用:)我这里的得出的编译错误是这样(error C2248: 'Fruit::name' : cannot access private member declared in class 'Fruit'),也就是name是私有成员,不能调用。不仅可以声明友元函数,还可以声明友元类。效果类似。看下面的例子。

例5.2:


#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
 friend class Person;        //声明一个友元类Person
public:
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name;
 }
 Fruit(const string &nst = "apple",const string &cst = "green"):name(nst),colour(cst){}  //构造函数
 
};
class Person             //定义类Person
{
string likedFruit;
public:                                   
 string name;                                   
 bool isLike(Fruit &aF)                                               
 {
  return likedFruit == aF.name;                //注意,他调用了Fruit类的私有成员,这本来是不允许的
 }
 Person(const string &npe = "jim",const string &lF = "apple"):name(npe),likedFruit(lF){}
};
int main()
{
 Fruit apple;
 Fruit orange("orange","yellow");
 Person jim;
 cout<<"Is "<<jim.name<<"like ";
 apple.print();
 cout<<"? : "<< jim.isLike(apple)<<endl;     //看看这个函数的调用

 
    return 0;
}

 


bool isSame(Fruit &aF,Fruit &bF,Fruit &cF)
{
 return (aF.name == bF.name)&&(bF.name == cF.name);
}

 具体Person类和程序到底是什么意思,我想只要你看了我以前写得东西,应该很明白了,就不多注释和讲了,我现在主要是讲友元(friend)的用途。另外一点重载函数的话,你想让几个成为友元就让几个,其他的将不是友元函数,这里提醒一下,重载函数其实可是各自独立的函数,只不过在C++中为了调用方便,让他们叫同一个名字而已。你不相信,可以自己试试。比如说在例5.1中,假如你重载一个但是却不声明为友元,编译是通不过的。你必须这样各自声明。见下例。

例5.3:


#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
 friend bool isSame(Fruit &aF,Fruit &bF);                          //声明为友元
 friend bool isSame(Fruit &aF,Fruit &bF,Fruit &cF);         //再次声明为友元

public:
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name;
 }
 Fruit(const string &nst = "apple",const string &cst = "green"):name(nst),colour(cst){}  //构造函数
 
};
bool isSame(Fruit &aF,Fruit &bF)
{
 return aF.name == bF.name;
}
bool isSame(Fruit &aF,Fruit &bF,Fruit &cF)
{
 return (aF.name == bF.name)&&(bF.name == cF.name);
}

int main()
{
 Fruit apple;
 Fruit apple2;
 Fruit apple3;
 Fruit orange("orange","yellow");
 cout<<isSame(apple,apple2)<<isSame(apple,apple2,orange)<<endl;

 return 0;
}

 

现在再回过来看例4.0。


#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
 
public:
 bool isSame(const Fruit &otherFruit)   //期待的形参是另一个Fruit类对象,测试是否同名
 {
  return name == otherFruit.name;
 }
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst,const string &cst = "green"):name(nst),colour(cst){}  //构造函数
 
 Fruit(){}
};

int main()
{
 Fruit apple("apple");
 Fruit orange("orange");
 cout<<"apple = orange ?: "<<apple.isSame(orange)<<endl;  //没有问题,肯定不同
 cout<<"apple = /"apple/" ?:"<<apple.isSame(string("apple")); //用一个string做形参?
 
    return 0;
}

除了隐式类类型转换外你还发现什么没有?恩,就是isSame()函数他直接调用了另一个引用的Fruit对象的私有成员name,这个按道理是不允许的啊,不过,注意的是,他本身就是Fruit类,所以,我个人看法(纯粹个人看法),这里可以认为一个类,自动声明为自己类的友元。呵呵,不知道对不对。假如你想这样定义,

bool Fruit::isSame(const string &otherName) 

{
  return name == otherName;
 }

 然后这样调用, cout<<apple.isSame(apple2.name)<<endl;结果是通不过编译的,道理还是不能调用一个类的私有成员。最后要说的是,我以前以为友元虽然为我们带来了一定的方便,但是友元的破坏性也是巨大的,他破坏了类的封装,不小心使用的话,会打击你对C++类安全使用的信心,就像强制类型转换一样,能不用就不用。但是当我看了Bjarne Stroustrup 的书后,才理解了一些东西,他的意思就是友元是没有人们说的那样的破坏性的,因为友元的声明权完全在类设计者手里,他能很好控制,而不会让友元的特性泛滥,而且在我学的更多一些后,发现友元在一些应用中必须得用到,比如一些操作符的重载,不用友元就不行,虽然个人感觉,类中成员函数省略的This形参假如没有友元作补充支撑,根本就不敢用,因为会限制很多功能,当然有了友元就没有关系了,可以在外面定义嘛。友元就讲了这么多,不知道是不是复杂化了。 

阅读全文....

浅谈C++类(4)--隐式类类型转换

欢迎转载,但请标明作者 “九天雁翎”,当然,你给出这个帖子的链接更好。

老规矩,看个例子,知道我要说的是什么。

例4.0:

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
 
public:
 bool isSame(const Fruit &otherFruit)   //期待的形参是另一个Fruit类对象,测试是否同名
 {
  return name == otherFruit.name;
 }
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst,const string &cst = "green"):name(nst),colour(cst){}  //构造函数
 
 Fruit(){}
};

int main()
{
 Fruit apple("apple");
 Fruit orange("orange");
 cout<<"apple = orange ?: "<<apple.isSame(orange)<<endl;  //没有问题,肯定不同
 cout<<"apple = /"apple/" ?:"<<apple.isSame(string("apple")); //用一个string做形参?
 
    return 0;
}

你会发现最后的使用上,我们用一个string类型作一个期待Fruit类形参的函数的参数,结果竟然得出了是true(1),不要感到奇怪,这就是我现在要讲的东西,隐式类类型转换:“可以用单个实参来调用的构造函数定义了从形参类型到该类型的一个隐式转换。”(C++ Primer)首先要单个实参,你可以把构造函数colour的默认实参去掉,也就是定义一个对象必须要两个参数的时候,文件编译不能通过。然后满足这个条件后,系统就知道怎么转换了,不过这里比较严格:)以前我们构造对象的时候Fruit apple("apple")其实也已经有了一个转换,从const char *的C字符串格式,转为string,在这里,你再apple.isSame("apple")的话,蠢系统不懂得帮你转换两次,所以你必须要用string()来先强制转换,然后系统才知道帮你从string隐式转换为Fruit,当然其实你自己也可以帮他完成。cout<<"apple = /"apple/" ?:"<<apple.isSame(Fruit("apple"));这样。参考例子1.2 :Fruit apple = Fruit("apple");  //定义一个Fruit类对象apple。也就是这样转换的。不过这就叫显式转换了,我们不标出来,系统帮我们完成的,叫隐式的贝。这里要说的是,假如你显示转换就可以不管有多少参数了,比如在前面提到的必须需要两个参数的构造函数时的例子。

例4.1:

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
 
public:
 bool isSame(const Fruit &otherFruit)   //期待的形参是另一个Fruit类对象,测试是否同名
 {
  return name == otherFruit.name;
 }
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst,const string &cst):name(nst),colour(cst){}  //构造函数
 
 Fruit(){}
};

int main()
{
 Fruit apple("apple","green");
 Fruit orange("orange","yellow");
 cout<<"apple = orange ?: "<<apple.isSame(orange)<<endl;  //没有问题,肯定不同
 cout<<"apple = /"apple/" ?:"<<apple.isSame(Fruit("apple","green")); //显式转换 
    return 0;
}

在你不想隐式转换,以防用户误操作怎么办?C++提供了一种抑制构造函数隐式转换的办法,就是在构造函数前面加explicit关键字,你试试就知道,那时你再希望隐式转换就会导致编译失败,但是,要说明的是,显式转换还是可以进行,出于不提供错误源代码例子的原则,错误的情况就不提供了,自己试试吧:)在说这个东西之前,我还不懂,现在我懂了:)我现在好像都习惯边学边讲了,有什么错误,你可要指出来啊。

阅读全文....

浅谈C++类(3)--两个特殊成员mutable与static成员

 欢迎转载,但请标明作者 “九天雁翎”,当然,你给出这个帖子的链接更好。

这一次讲我一直没有怎么搞明白的两个特殊类成员,mutable,static。

接着第(1)次的内容,从水果讲起。我们希望有一个成员总是可以被修改,即mutable。哪怕他是const成员函数都可以修改,这种需要感觉还是比较少有。不过我们可以看看例子。

例3.0:

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
 mutable double price;   //这是一个新成员,mutable的成员
public:  
 void chaPri(const double &newpri)const     //这是一个const成员函数
 {
  price = newpri;
 }
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<" is priced at "<<price<<endl;
 }
 Fruit(const string &nst,const string &cst = "green",const double &newpri = 0.0):name(nst),colour(cst),price(newpri)  //构造函数
 {}
 Fruit(){}
};
int main()
{
 Fruit apple("apple");  //定义一个Fruit类对象apple
 apple.chaPri(10.0);       //看到没有?这里用const成员函数改变了price
 apple.print();         //为什么可以这样?因为price是mutable的
    return 0;
}

当然这里的例子没有什么实际意义,因为不把chaPri()函数定为const就可以不用mutable了,在C++ Primer中提到实际的使用是在一个const成员函数需要统计自己调用的次数时,可以使用这个计数。假如你还没有明白mutable的作用的话,你把mutable去掉试试,看看编译错误的提示,我用vc++2005得出的是error C2166: l-value specifies const object,不太懂什么意思,大概就是说const成员函数不能利用左值修改成员。也不知道到底是什么意思。

现在讲讲static,static在C里面就有,在讲静态局部变量的时候讲了,大概就是生命周期是全局的这么一个概念,在类里面用static定义的成员,叫类静态成员。我们看看他到了类里面是怎么样的。首先有个心理准备,这是我目前见过的最扭曲不合情理思维方式的东西。

例3.1 :


#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
 static string beFrom;    //定义一个static数据成员
public:  
 static string whFrom()          //定义一个static成员函数
 {
  return beFrom;
 } 
 
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst,const string &cst = "green"):name(nst),colour(cst)  //构造函数
 {}
 Fruit(){}
};
string Fruit::beFrom = "Hunan";     
//必须在类外部定义一次(也只能一次),而且此时初始化
int main()
{
 Fruit apple("apple");  //定义一个Fruit类对象apple
 apple.print();
 cout<<apple.whFrom()<<endl;

    return 0;
}

 

首先,扭曲之一,你不能直接使用,还需要在类外面定义一次。扭曲之二,你不能在main()函数里面定义,那样是重复定义错误,必须在main()函数外面定义。扭曲之三,初始化不能在构造函数中进行,必须在第2次定义时进行。(其实这里看来,类里面那次定义应该看作声明)。这几点扭曲的地方让我百思不得其解,理解了n久也没有太明白到底怎么使用才能减少错误。你有什么高见,请一定告诉我。这里还有个为static准备的特例,也就是可以当他为整形,并且声明为const的时候,他可以在类定义时就初始化,这点非static成员和非整形成员是无论如何也不可以的。见例子。

例3.2: 


#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
 static const int size = 10;    //定义一个const 整形 static数据成员
public:  
 static int reSize()          //定义一个static成员函数
 {
  return size;
 } 
 
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst,const string &cst = "green"):name(nst),colour(cst)  //构造函数
 {}
 Fruit(){}
};
int main()
{
 Fruit apple("apple");  //定义一个Fruit类对象apple
 apple.print();
 cout<<apple.reSize()<<endl;

 
    return 0;
}

这里扭曲的地方又多了,扭曲一:const static成员在类的定义体中初始化够特殊了吧?更特殊的是,只能是整形才能这样特殊,你试试,string什么的都不可以,int,long 等整形才可以。扭曲二 :在C++ Primer 第4版原版第470面,中文版第401面,明确提示,就算是这样初始化了,在类的定义体外使用前,必须还要定义一次,而实际上根本不需要了就可以使用,假如你认为我这个例子是用了成员函数间接调用的不可靠的话,你直接把他改为public直接调用看看,也可以的。我还为了预防最经常使用的C++.net2005不合标准,我用dev-c++4.990得出一样的结论。我一直把C++Primer看作经典,我有时宁愿怀疑是microsoft的编译器不合标准,也不怀疑书的错误,可是我又一次错了(我的书已经在网上下过最新的勘误表并改正过),前一次的错误是在第11章,泛型算法,原书第421面,中文版361面的list容器特有的操作表。lst.splice(iter,beg,end)根本就是错误,这里必须要指出第2个容器,即实际使用方法为lst.splice(iter,lst2,beg,end),大家可以去验证一下,在C++程序设计语言(Bjarne Stroustrup)里面我得到了正确答案,以前调试不成功我也怀疑过VC,不过VC还是对的。

下面我们来看几个static的使用,虽然我一直不太喜欢使用这种特殊化,扭曲化的东西,不过了解一下还是好的。

例3.3:


#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 string colour;   //定义一个colour成员
 
public:  
 static string beFrom;    //定义一个static数据成员
 static string whFrom()          //定义一个static成员函数
 {
  return beFrom;
 } 
 
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst,const string &cst = "green"):name(nst),colour(cst)  //构造函数
 {}
 Fruit(){}
};
string Fruit::beFrom("Hunan");        //注意初始化的是Fruit类的beFrom
int main()
{
 Fruit apple("apple");  //定义一个Fruit类对象apple
 Fruit banana("banana","yellow");
 apple.print();
 cout<<"come from " <<Fruit::whFrom()<<endl;//这里没有什么问题;
 banana.print();
 cout<<"come from " <<apple.whFrom()<<endl;
 //apple对象的成员beFrom也一样,因为static成员是属于大家的
 apple.beFrom = "Hubei";   //改变apple对象的beFrom
 banana.print();
 cout<<"come from " <<banana.whFrom()<<endl;//你会发现其实改变了大家的beFrom

 
    return 0;
}

 

大概就这样了,你理解了一点没有?我还是没有理解。。。。。。。

今天在Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1586006 看到作者提出effective C++中提出了这个static,“今天看effective c++,发现一个有趣的东东,就是关于一个static的用法,以前没有怎么在实际工作中用到。使用static定义成员函数的含义,整个类只有这个成员的一份拷贝,并且这个成员可以不通过类的具体对象来访问。也就是说用static定义的成员函数可以当成全局函数使用,太好用了,太伟大了,c++,我爱你!!!!!”可以看出作者的高兴之情,问题是,我也知道这个复杂扭曲的东西是设计来干什么的,不过我却觉得不需要这样设计,使用起来太复杂了。当然,这只是一个初学者的看法。

 

阅读全文....

浅谈C++类(2)--一个简单的多文件具体例子

上一次讲了一些基本的概念,这次分析一个稍微复杂但其实还是很简单的例子。现看定义:


Person.h

#include <map>
#include <string>

class Person
{
public:
Person(const std::string &,const std::string &);
Person(){};                                                                                                                                                                                     

void print(const std::string &) const;    //声明一个const成员函数输出
 void print();
 void input(const std::string &,const std::string &);
private:
 std::map<std::string,std::string> aperson;
}; 

 


Person.cpp

#include "Person.h"
#include <iostream>
#include <iterator>
#include <algorithm>

Person::Person(const std::string &name,const std::string &adress)  //一个构造函数
{
 aperson[name] = adress;
}

void Person::print(const std::string &name) const      //定义了一个const成员函数,输出
{
 std::map<std::string,std::string>::const_iterator it = aperson.find(name); 

  //在aperson中查找key name
 if(it != aperson.end())                            //假如有就输出
 {
  std::cout<<it->first<<"'s adress is "<<it->second<<std::endl;
 }
 else
 {
  std::cout<<"there is no this person called"<<name<<std::endl;
 }
}
void Person::print()                               //重载一个输出,没有参数就输出所有的值
{
 for(std::map<std::string,std::string>::iterator it =aperson.begin(); //遍历aperson
  it != aperson.end();++it)
 {
  std::cout<<it->first<<" in "<<it->second<<std::endl;
 }
}

void Person::input(const std::string &name, const std::string &adress)

         //用input管理输入
{
 aperson[name] = adress;                  //有就改名,没有就增加一个,利用map下标的特点
}

 

这个类使用的方法具体可以参考下面,你也可以自己试试:


#include "Person.h"
#include <string>

int main()     
{
 Person abook("jack","Guanzhou");
 abook.input("jim","Beijing");
 abook.input("tom","Hunan");
 abook.print("jack");

 return 0;
}

在这里注意,和以前说的不同的是,类的被分开放在两个文件中,而且,类中的函数都在类定义外面定义,这里这样做是因为既然要封装,那么类的使用者就没有必要知道第2个文件,知道第一个文件就够了,只要你详细说明了每个函数的实际用途,类的使用者可以不关系他的具体实现,节省时间和精力。这是雷同于C中编写不同的函数的程序员之间也不需要实际了解那个函数具体怎么实现的一样,C就是这样实现结构化编程的,而C++就是通过这样写类,来实现面向对象编程的。在类外定义类的成员函数,你必须指明,这个函数是某个类的函数,通过符号“::”,在上面的例子中很明显。另外,所谓的const成员函数,表示这个函数不能更改类中的数据,在声明及定义中都必须加上const.public下的东西可以公共访问,所以被叫做一个类的接口,什么叫接口?就是和外部产生联系所需要的东西。private下的别人就不需要了解了,叫封装,也是一种黑盒原理。可以看到,这里有2个接口函数,print(),input();其中print()有一个重载版本。还有1个重载的构造函数。看名字都很好理解。数据中就定义了 一个由2个string组成的map。这里把类的定义放在一个单独的头文件里面,所以都用了std::而没有用using namespace std,因为头文件中最好不要用这个。其实在小程序中倒也无所谓。

阅读全文....

浅谈C++类(1)--概念和构造函数

欢迎转载,但请标明作者 “九天雁翎”,当然,你给出这个帖子的链接更好。

类多么重要我就不多说了,只讲讲学习,因为个人认为类的学习无论从概念的理解还是实际代码的编写相对其他C兼容向的代码都是比较有难度的, 对于以前学C 的人来说这才是真正的新概念和内容,STL其实还比较好理解,不就是一个更大的函数库和代码可以使用嘛。虽然vector,string就是类,不过我们却不需要这样去理解他们,就可以很好的使用了。

先说明,1,这是非常初级的东西。2,你懂了就不需要看了。3,我写出来是帮助还不懂得人。4,我自己也还不太懂,所以才写下来,梳理一下,希望自己能更好的理解,因为我相信一句话,很好的理解一个东西的好方法是把这个东西教给别人。有什么不对的地方,欢迎指出,我非常感谢,还有很多时候,某种方法是不允许的,了解也很重要,但我不想给出错误的例子,那样很容易给出误导,只讲这样是错误的,希望你可以自己输入去尝试一下,看看得出的是什么错误。

一、概念:就Bjarne Stroustup自己说,来自于Simula的概念(The Design and Evolution of C++),我不懂Simula,所以,还是对我没有什么帮助,基本上,都说类是具体对象(实例)的抽象,怎么抽象?就是把一个实例的特征拿出来,比如,水果是一个类,苹果就是一个实例,苹果有水果的特征。我们只要从苹果香蕉中把特征抽象出来“class Fruits{ }”;就好了。然后 “Fruits apple”,表示苹果是一个水果。就像人是一个类的话,我们就都是实例。一下也讲不清,不过也可以从另一个角度去理解,就是Bjarne Stroustup自己说的,一个Class其实就是一个用户定义的新Type,这点上和Struct没有什么本质上的区别,只是使用上的区别而已。之所以没有把它直接叫作Type是因为他的一个不定义新名字的原则。

二、使用:我一直觉得比较恼火,光看概念是没有用的,学习程序,自己编写代码是最快的。下面是几个步骤:

1:最简单的一个类。

C++中使用任何东西都要先定义吧,类也不例外。用水果举例,水果的特征最起码的名字先这1个吧。名字用string表示。

例1.0:

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
public:                //标号,表示这个类成员可以在外部访问
 string name;          
};
int main()
{
 Fruit apple = {"apple"};  //定义一个Fruit类对象apple
 cout<< apple.name<<endl;  //使用apple的成员name
 return 0;
}

在这里说明,以后其他细节我都省略说明了,比如#include,using,cout等等,先去学会吧。我只说类;你会发现其实在这里把class换成struct没有任何问题,的确,而且换成sturct后"public:" 标号都可以省略,记住,在C++里面,struct与class其实没有本质的区别,只是stuct默认成员为public而class默认为private。public顾名思义,就是公共的,谁都可以访问,private自然就是私人的,别人就不能访问了,你把例1.0的public:标号这行去掉试试。你会得到两个错误,1,不能通过 Fruit apple = {"apple"};形式定义,2,cout<<行不能访问私有成员。这里class几乎就和c里面的struct使用没有区别,包括apple.name点操作符表示使用对象apple里面的一个成员,还有Fruit apple = {"apple"};这样的定义初始化方法。很好理解吧,不多说了。说点不同的,C++里面class(struct)不仅可以有数据成员,也可以有函数成员。比如,我们希望类Fruit可以自己输出它的名字,而不是我们从外部访问成员。

例1.1:

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
public:                //标号,表示这个类成员可以在外部访问
 string name;            //定义一个name成员          
 void print()              //定义一个输出名字的成员print()
 {
  cout<< name<<endl;
 }
};
int main()
{
 Fruit apple = {"apple"};  //定义一个Fruit类对象apple
 apple.print();  //使用apple的成员print
 return 0;
}

这里你会发现与C的不同,而这看起来一点点地不同,即可以在class(struct)中添加函数成员,让C++有了面向对象特征,而C只能是结构化编程(这在C刚出来的时候也是先进的代表,不过却不代表现在的先进编程方法)。还有,你发现定义函数成员和定义普通函数语法是一样的,使用上和普通成员使用也一样。再进一步,在C++中有构造函数的概念,先看例子

例1.2:

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
public:                //标号,表示这个类成员可以在外部访问
 string name;            //定义一个name成员          
 void print()              //定义一个输出名字的成员print()
 {
  cout<< name<<endl;
 }
 Fruit(const string &st)      //定义一个函数名等于类名的函数成员
 {
  name = st;
 }

};
int main()
{
 Fruit apple = Fruit("apple");  //定义一个Fruit类对象apple
 Fruit orange("orange");
 apple.print();  //使用apple的成员print
 orange.print();    
 return 0;
}

例子1.2里面的函数名等于类名的函数成员就叫作构造函数,在每次你定义一个新对象的时候,程序自动调用,这里,定义了2个对象,一个apple, 一个orange,分别用了2种不同的方法,你会发现构造函数的作用,这里,要说的是,假如你还按以前的方法Fruit apple = {"apple"}定义apple你会编译失败,因为有了构造函数了,Fruit apple就定义成功了一个对象,让apple对象等于{"apple"}的使用是不允许的。对象只能等于对象,所以你可以先用Fruit("apple")构造一个临时的对象,然后让apple等于它。orange对象的定义就更好理解了,直接调用构造函数嘛。这里要说的是,你不可以直接Fruit banana了,因为没有可以用的构造函数,而没有用构造函数前,你是可以这样做的。直接Fruit apple,再使apple.name = "apple",是完全可以的。

例1.3:

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
public:                //标号,表示这个类成员可以在外部访问
 string name;            //定义一个name成员          
 void print()              //定义一个输出名字的成员print()
 {
  cout<< name<<endl;
 }

};
int main()
{
 Fruit apple;  //定义一个Fruit类对象apple
 apple.name ="apple";  //这时候才初始化apple的成员name
 apple.print();  //使用apple的成员print
  
 return 0;
}

而有了构造函数以后就不能这样了,怎么样不失去这种灵活性呢?你有两种办法。其一是重载一个空的构造函数,记得,构造函数也是一个函数,自然也可以重载罗。你还不知道什么是重载?那先去学这个简单的东西吧,类比那家伙复杂太多了。

例1.4:

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
public:                //标号,表示这个类成员可以在外部访问
 string name;            //定义一个name成员          
 void print()              //定义一个输出名字的成员print()
 {
  cout<< name<<endl;
 }
 Fruit(const string &st)
 {
  name = st;
 }
 Fruit(){}    //重载一个空构造函数
};
int main()
{
 Fruit apple;  //定义一个Fruit类对象apple,这时是允许的了,自动调用第2个构造函数
 apple.name ="apple";  //这时候才初始化apple的成员name
 apple.print();  //使用apple的成员print
  
 return 0;
}

第二种办法,就是使用构造函数默认实参;

例1.5

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
public:                //标号,表示这个类成员可以在外部访问
 string name;            //定义一个name成员          
 void print()              //定义一个输出名字的成员print()
 {
  cout<< name<<endl;
 }
 Fruit(const string &st = "banana")
 {
  name = st;
 }
};
int main()
{
 Fruit apple;  //定义一个Fruit类对象apple
 apple.print();
 apple.name ="apple";  //这时候才初始化apple的成员name
 apple.print();  //使用apple的成员print
  
 return 0;
}

这个程序里面,当你直接定义一个无初始化值的apple对象时,你发现,他直接把name表示为banana。也许现在你会问,为什么需要构造函数呢?这里解释以前留下来的问题。即不推介使用Fruit apple = {"apple"}的原因。因为这样初始化,你必须要保证成员可以访问,当name为私有的时候,这样可就不奏效了,为什么需要私有呢?这就牵涉到类的数据封装问题,类有不希望别人访问的成员,以防破坏内部的完整性,也以防误操作。这点上要讲就很复杂了,不多讲了。只讲操作吧。

例1.6

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
               //没有标号了,表示这个类成员不可以在外部访问,class默认为private哦
 string name;            //定义一个name私有成员          
public:  
 void print()              //定义一个输出名字的成员print()
 {
  cout<< name<<endl;
 }
 Fruit(const string &st = "banana")
 {
  name = st;
 }
};
int main()
{
 Fruit banana;  //定义一个Fruit类对象

 banana.print();
// banana.name ="apple";  //这时候才改变banana的成员name已经是不允许的了
//  你要定义一个name等于apple的成员必须这样:
 Fruit apple("apple");
 apple.print();
  
 return 0;
}

要说明的是,构造函数你必须定义成公用的啊,因为你必须要在外部调用啊。现在讲讲构造函数特有的形式,初始化列表,这点和一般的函数不一样。

例1.7:

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
  string name;            //定义一个name成员          
public:  
 void print()              //定义一个输出名字的成员print()
 {
  cout<< name<<endl;
 }
 Fruit(const string &st = "banana"):name(st){}  //看到不同了吗?
};
int main()
{
 Fruit banana;  //定义一个Fruit类对象

 banana.print();
  
 return 0;
}

在参数表后,函数实体前,以“:”开头,列出的一个列表,叫初始化列表,这里初始化列表的作用和以前的例子完全一样,就是用st初始化name,问题是,为什么要特别定义这个东西呢?C++ Primer的作者Lippman在书里面声称这时许多相当有经验的C++程序员都没有掌握的一个特性,因为很多时候根本就不需要,用我们以前的形式就够了但有种情况是例外。在说明前我们为我们的Fruit加个固定新成员,而且定义后不希望再改变了,比如颜色。

例1.8:

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
  string name;     //定义一个name成员          
 const string colour;
public:  
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst = "apple",const string &cst = "green"):name(nst),colour(cst){} 
};
int main()
{
 Fruit apple;  //定义一个Fruit类对象apple
 apple.print();
  
 return 0;
}

在这里你把colour的初始化放到{}里面,用以前的那种方法,你会发现编译错误,因为它是const的,而实际上放在{}里面是个计算阶段,而放在初始化列表里面就可以,因为初始化列表的使用是在数据定义的时候就自动调用了,因为这个原因,数据的调用顺序和初始化列表里面的顺序无关,只和数据定义的顺序有关,给两个例子,比如你在上面的例子中把初始化列表改为":colour(name),name(nst)"没有任何问题,因为在定义colour前面,name 就已经定义了,但是":name(colour),colour(cst)"却不行,因为在name定义的时候colour还没有被定义,而且问题的严重性在于我可以通过编译.........太严重了,所以在C++ Primer不推荐你使用数据成员初始化另外一个数据,有需要的话,可以":name(cst),colour(cst)",一样的效果。另外,初始化列表在定义时就自动调用了,所以在构造函数{}之前使用,你可以看看这个例子:

例1.9 :

#include <string>
#include <iostream>
using namespace std;
class Fruit               //定义一个类,名字叫Fruit
{
 string name;     //定义一个name成员          
 const string colour;
public:  
 void print()              //定义一个输出名字的成员print()
 {
  cout<<colour<<" "<<name<<endl;
 }
 Fruit(const string &nst = "apple",const string &cst = "green"):name(nst),colour(cst)
 {
 name +="s";    //这时name已经等于"apple“了
 } 
};
int main()
{
 Fruit apple("apple","red");  //定义一个Fruit类对象apple
 apple.print();
  
 return 0;
}

最后输出red apples。先讲到这里吧,你明白一点什么是类没有?像我一样学了老半天还不明白的,坚持住,多练习,总能明白的。我现在似乎明白的多一点了:)

阅读全文....

用正版还是盗版

以前一直用金山词霸的盗版,当然用的盗版还不止这一个,反正好像卖盗版才犯法,用却不会,何况也没有见现在卖盗版的有什么违法,公开卖的那么happy,有段时间不想用盗版,用过Linux,那时的习惯把statdict带入了windows下来用,用了一段时间发现金山词霸还是比较好,然后又改用金山词霸,也没有什么不妥,最近新上电信宽带,免费送金山的使用权一年,给了个通行证,想想,有正版还是用正版吧,结果通行证在界面下输入一次卡号密码(1),登陆半天没有反应,再输入一次卡号密码(2)最后又说密码错误,只好到金山主页登陆通行证输入一次卡号密码(3)上去看看,也没有折腾明白,提示没有这个用户,然后验证卡号是不是假的,再申请一个通行证,用我的卡号,输入一次卡号(4)提示已存在 后来看有新手卡,输入一次卡号密码(5)想试试,果然是的,不过送的新手卡还不是这个新手卡,然后总算给了个连接去激活属于金山词霸的新手卡的页面(没有看到可以从其他的地方进来),结果满以为可以了,输入一次卡号密码(6)还给我个

说明: 服务器忙,请稍候重试

返回
郁闷!!!还要重新输入一次卡号密码(7)再次告诉我
说明: 服务器忙,请稍候重试

我是不明白,金山公司至于穷成现在这个样子?现在是早上9点多,还不是网络最繁忙的时候,服务器就这么忙啊?以前对金山公司的好印象一下子都没有了。。。。。。
我是郁闷了,以前用的盗版,在讯雷下一个,安装后,拷贝2个文件,运行一次,一切都OK了,这就是用正版的代价 。。。。。。。。。。。
气愤之余,删了正版的下载版,重新装上盗版,还是那么方便快捷。。。。。。

阅读全文....