定义一个类:
Class 类名{
声明
};
声明中可包含数据声明、函数声明。成员默认情况下都是private的,换言之,它们不能被访问。另外需要注意的是,类的声明最末尾需要有一个分号“;”。
在C++中,struct和class关键字是等价的,唯一的区别就是struct的成员默认为public。这两个关键字在C++中实际都会创建类。这同时意味着,术语“类”和关键字class并不是严格对应的。
C++之所以保留struct定义,完全是为了保持与C的向下兼容性。在面向对象程序设计中,只有在有充分理由的前提下,才考虑让一个成员成为public成员。
成员函数定义:
Type class_name::function_name(argument_list){
statements
}
#include<iostream>
using namespace std;
class Point{
int x,y;
public:
void set(int newx,int newy);
int get_x();
int get_y();
};
int main(int argc, char* argv[])
{
Point pt1,pt2;
pt1.set(10,20);
cout<<"pt1 is "<<pt1.get_x()<<","<<pt1.get_y()<<endl;
pt2.set(-15,-2);
cout<<"pt2 is "<<pt2.get_x()<<","<<pt2.get_y()<<endl;
}
void Point::set(int newx,int newy){
if(newx<0) newx*=-1;
if(newy<0) newy*=-1;
x=newx;
y=newy;
}
int Point::get_x(){
return x;
}
int Point::get_y(){
return y;
}
内联函数
内联函数就是在一个类的声明时直接定义函数内容,而不是在函数外部定义。记住*此时函数的末尾不能添加分号。
函数内联之后,编译器调用不会将控制转移到一个新的程序位置。相反,编译器会将函数调用替换成函数主体。
函数内联后可以提升效率!假如函数采取的行动只是几条机器指令(比如将数据从一个特定内存位置移动到另一个位置),就可以将其写成内联函数,从而改善函数执行效率。
但是,假如函数包含的并不仅仅是几个简单的语句,就不应内联。因为只要函数被内敛,编译器就会将整个函数主体嵌入函数调用位置。假如一个内联函数经常被调用,程序占用的空间会无谓增大。除此之外,内联函数不能递归。
class Point{
int x,y;
public:
void set(int new_x,int new_y){
x=new_x;
y=new_y;
}
int get_x(){return x;}
int get_y(){return y;}
};
构造函数
默认构造函数
构造函数(constructor)是C++用于描述“初始化函数”的一个术语。例如:Point p(1,2);
构造函数本质上是一种特殊的成员函数,它必须在类的内容声明。语法如下:
class_name(argument_list){
statements }
在类的声明之外定义的一个构造函数具有如下语法形式:
class_name::class_name(argument_list){
statements }
这个函数没有返回类型。在某种意义上,类名代替了返回类型。我们可以使用同一个函数名来创建几个不同的函数,C++编译器根据参数列表(argument_list)中包含的类型来区分。一个函数名对应多个函数称为函数重载。
如果没有声明构造函数,系统会自动生成默认构造函数。例如:Point(){};所以我们才能声明无参数对象,Point a;
一旦我们声明了构造函数,默认构造函数就会被覆盖掉,此时便不能再声明无参数的对象了。Point a;//错误!没有默认构造函数!
对于编译器提供的默认构造函数,它的行为是将所有数据成员设为零。Char字符串中所有位置用null填充,指针都设为null指针。
代码演示(默认构造函数)
#include<iostream>
using namespace std;
class Point{
int x,y;
public:
Point(){};
Point(int new_x,int new_y){
set(new_x,new_y);
};
void set(int newx,int newy);
int get_x();
int get_y();
};
int main(int argc, char* argv[])
{
Point pt1,pt2;
Point pt3(5,10);
cout<<"pt1 is "<<pt1.get_x()<<","<<pt1.get_y()<<endl;
cout<<"pt3 is "<<pt3.get_x()<<","<<pt3.get_y()<<endl;
}
void Point::set(int newx,int newy){
if(newx<0) newx*=-1;
if(newy<0) newy*=-1;
x=newx;
y=newy;
}
int Point::get_x(){
return x;
}
int Point::get_y(){
return y;
}
拷贝构造函数(copy constructor)
如果不写拷贝构造函数编译器会自动为你提供。并且不会因为自己写了一个构造函数就停止自动提供的构造函数。下面列出了自动调用拷贝构造函数的情况:
1、一个函数的返回值类型是类时。函数创建对象的一个拷贝,然后把它传回调用者。
2、一个参数类型是类时。这时会创建参数的一个拷贝,并把它传给函数。
3、使用一个对象来初始化另一个对象时,例如:
Point a(1,2);
Point b(a);
声明拷贝函数语法:
Class_name(class_name const &source);
注意其中使用的const关键字。该关键字确保参数不会被函数更改。编译器提供的拷贝构造函数的行为是逐个拷贝每一个数据成员。
这里要非常注意一点:默认的拷贝构造函数是浅复制,最好自己写拷贝构造函数!!!
深拷贝和拷贝构造函数
为了将一个对象拷贝到另一个,最容易想到的办法就是采取默认(编译器提供的)拷贝构造函数的做法:执行一次简单的、逐个成员的拷贝。
而默认拷贝构造函数则是:将Ptr的值直接拷贝,使新对象和第一个对象都指向同一个内存。
这种做法在某些情况下是可行的,但存在一个问题:如果str2指向的数据出了什么事,那该怎么办?具体来说,假如str2超出了作用域,或者因为某个原因而被删除,那么str1对象也会消失。
执行下面代码:
String str1(“hello”);
str1=”cat”;
cout<<str1;
你会发现系统并没有打出cat,而是打出乱码。原因是这样的:在执行到str1=”cat”;语句时,编译器没有找到operator=(const String&)函数,但是operator=函数要求等号后面类型为String。所以,编译器将cat通过String(char *s)构造函数转换成为String类型。str1中ptr指向新构造出的String对象。问题是当结束语句时临时创建的String cat对象会调用析构函数,回收字符串占用的空间,这时str1.ptr就指向了一个已经删除的空间。
为了避免这种问题,你必须使用“深拷贝”(deep copy)。换言之,当你拷贝一个对象时,它必须能够重构它的全部内容,而不仅仅是拷贝值。在String类的情况下,我们需要一个完整的拷贝函数。
String::String(const String &src){
int n;
ptr=new char[n+1];
strcpy(ptr,src.ptr); }
事实上,即便是有了完整的拷贝函数,之前的语句也不能正常执行。因为默认的赋值函数也有问题。它是浅赋值的!!!操作符函数
类操作符函数入门
return_type operator@(argument_list)
使用上述语法时,需要将符号@替换成一个有效的C++操作符,比如+、-、*和/。事实上,C++标准类型支持的任何操作符都能在这里使用。
可以将一个操作符函数定义为成员函数或者全局函数。如果声明为成员函数,左操作数就是该“操作符函数”的调用函数,右操作数是该“操作符函数”的参数;如果声明为全局函数,那么两个操作数都是该“操作符函数”的参数。

操作符函数作为全局函数

下面是函数的定义:
Point operator+( Point pt1,Point pt2){
Point new_pt;
new_pt.x=pt1.x+pt2.x;
new_pt.y=pt1.x+pt2.y;
return new_pt;
}
利用引用提高效率
Class Point{
//…
Public:
Point add(const Point &pt);
Point operator+(const Point &pt) {
return add(pt);
}
};
Point Point::add(const Point &pt){
Point new_pt;
new_pt.x=x+pt.x;
new_pt.y=y+pt.y;
return new_pt;
}
这里使用了const关键字,它的作用是防止对传递的参数进行修改。函数获得它自己的参数拷贝之后,不管你在函数内部做什么,都无法改动原始拷贝的值。内联operator+函数之后,一旦在程序中遇到pt1+pt2这样的运算,就会将其直接转换成对add函数的调用。
代码演示(copy constructor、操作符函数)
#include<iostream>
using namespace std;
class Point{
private:
int x,y;
public: //构造函数
Point(){};
Point(int new_x,intnew_y){
set(new_x,new_y);
};
Point(const Point &scr){
set(scr.x,scr.y);
}[A1] //这里的【A1】是我在word文档里的批注,各位可以在文章最下面看到。
//数学运算
Point add(const Point &pt);
Point sub(const Point &pt);
Point operator+(const Point &pt){
return add(pt);
}
Point operator-(const Point &pt){
return sub(pt);
}
void set(int newx,int newy);
intget_x() const{
return x;}
int get_y() const{
return y;}[A2] [A3]
};
int main(int argc, char* argv[])
{
Point pt1(-10,25),pt2(0,0);
Point pt3(5,10);
Point pt4=pt1+pt2-pt3;
cout<<"pt4 is"<<pt4.get_x()<<","<<pt4.get_y()<<endl;
}
void Point::set(int newx,int newy){
if(newx<0) newx*=-1;
if(newy<0) newy*=-1;
x=newx;
y=newy;
}
int Point::get_x(){
return x;
}
int Point::get_y(){
return y;
}
Point Point::add(const Point&pt){
Point new_pt;
new_pt.x=x+pt.x;
new_pt.y=y+pt.y;
return new_pt;
}
Point Point::sub(const Point&pt){
Point new_pt;
new_pt.x=x-pt.x;
new_pt.y=y-pt.y;
return new_pt;
}
友元函数
一个外部的函数想引用类内部私有数据成员该怎么办呢?
下例为Point类声明了一个友元函数:
Class Point{
//…
Public:
Friend Point operator+(Point pt1,Point pt2);
}
类赋值函数(=)
赋值操作符函数(operator=)的特殊性在于,假如你自己没有写,编译器就会自动提供一个。正是这个原因,我们在以前的程序中才能直接执行下面的运算:
Point pt4=pt1+pt2-pt3;
赋值操作符函数只能将值拷贝到一个现有的函数(就是把地址引过去),而拷贝函数可以自动初始化一个新对象(但在某些情况下会自动消亡)。
class_name&operator=(const class_name &source_arg)
注意,虽然上述声明类似于拷贝构造函数,但operator=函数必须返回一个对象的引用,同时还要获取一个引用参数。
class Point{
//…
public:
Point&[A4] operator=(constPoint &src){
set(src.x,src.y);
return *this;[A5]
}
};
对于这样的类,没必要专门写一个赋值操作符函数。完全可以利用它的默认行为;而且假如你没有提供赋值操作符函数,编译器肯定会自动提供一个。
这里要非常注意一点:默认的赋值函数是浅赋值,使用时千万小心,最好自己写赋值函数!!!
测试相等性函数(==)
编译器不会为你的类自动提供一个operator==函数。所以就只能自己写喽:
class Point{
//…
public:
bool operator==(const Point &other){
return (x==other.x&&y==other.y);
}
};
输出流函数(<<)
friend ostream &operator[A6] <<(ostream&os,Fraction &fr);
ostream &operator<<(ostream &os,Fraction &other){
os<<other.num<<"/"<<other.den[A7] ;
return os;
}
隐式类型转换
隐式类型转换运算符只是一个样子奇怪的成员函数:operator关键字,其后跟一个类型符号。你不用定义函数 返回类型,因为返回类型就是这个函数的名字。例如,为了允许rational(有理数)类隐式地转换为double类型(在有理数进行混合运算时可能有用),你可以如此声明rational类:
class Rational{
public:
…
operator double() const ;
};
在下面的情况下函数会被自动调用:
Rational r(1,2); //r的值是1/2。
Double d=0.5*r; //转换r到double,然后进行乘法。
最好不要定义类型转换函数,因为它们总会在你不需要转换函数时偷偷执行。例如:
Rational r(1,2);
cout<<r;
再假设你忘了为Rational定义operator<<函数。你可能认为它会打印失败,因为没有合适的operator<<函数,但是你错了。当编译器调用operator<<发现没有这个函数时,编译器会试图找到一个合适的隐式类型转换使程序正常运行。类型转换顺序的规则是复杂的。一般来说,越有经验的 C++程序员就越喜欢避开类型转换运算符,通过单参数构造函数进行隐式类型转换更难消除。
详细可见:《More effectiveC++》 item M5。
new操作符
语法:new类型
如:int*p;
P= new int;
上述语句的结果是创建一个未命名的整数变量,它只能通过P来访问。
整数值不是在程序中声明的,而是在程序运行时动态创建的。换言之,整数值是在程序执行期间分配的,而不是在程序加载到内存的时候分配的。这样一来,程序就能在需要的时候,自由地创建新的数据空间。
与new操作符对应的还有一个delete操作符
语法:delete 指针;
该语句的作用是销毁指针所指的数据项,将它占用的内存归还给操作系统。
对象和new
new操作符既支持基本数据类型,也支持对象类型(类)。事实上,设计new主要目的就是操纵类,虽然它在操纵int和double这样的类型时,也相当有用。
用new和一个参数列表为一个对象分配空间的语法是:
new class_name(argument_list)
这个表达式将为指定类分配一个对象所需要的空间,然后调用恰当的构造函数,并返回对象地址。
Point *p=newPoint; 或 Point *p=new Point(1,2);
调用对象函数时,可以写成:(*p).get_x();
C++提供了更为简洁的代码:p->get_x();
为数组数据分配空间
C++允许你在运行时使用new操作符来声明一个内存块,语法如下:
new type[size]
上述表达式将为size个元素分配空间,其中每个元素都具有指定的type。然后返回第一个元素的地址。type可以是一个标准类型(int、char、double),也可以是一个类。size值是一个整数。
Int *p=new int[50];
p[0]=1;
p[5]=0
重点来了!!!重点在于你指定的size不一定是一个常量。这是C语言不允许的。你可以在运行时决定需要多大的内存。例如:
int n;
cout<<”有多少个元素”;
cin>>n;
int *p=new int[n];
使用任何形式的new操作符时,都要由你负责内存的创建和回收。所以,到程序结束时,记住使用delete操作符来回收任何新建的内存对象。
delete [] pointer; (回收该内存块)
如果请求的内存不可用,new操作符会返回一个null指针。
int *p=new int[1000;
if(!p){
cout<<”内存不足”;
exit(0);
}
可能发生另一个问题是内存泄漏(memory leak)。使用new成功请求内存后,操作系统会帮你保留内存块,直到delete回收,或是计算机重启。
演示代码
#include<iostream>
using namespace std;
int main(){
int n,sum=0;
int *p;
cout<<"Enternumber of items: ";
cin>>n;
p=new int[n];[A8]
for(int i=0;i<n;i++){
cout<<"Enteritem #"<<i<<":";
cin>>p[i];
sum+=p[i];
}
cout<<"Here arethe items: ";
for(int i=0;i<n;i++){
cout<<p[i]<<",";
}
cout<<endl;
cout<<"The totalis: "<<sum<<endl;
cout<<"Theaverage is :"<<static_cast<double>(sum)[A9] /n<<endl;
delete []p;[A10]
return 0;
}
this关键字
简单来说,this关键字是指向当前对象的一个指针。所谓“当前对象”是指,通过它来调用一个成员函数的对象。This关键字只有在类的成员内部才有意义。
class Fraction{
private:
int num,den; //number denominator
public:
……
void set(int n,int d){
num=n;
den=d;
normalize();
}
};
对Fraction类成员函数调用:
fract1.set(1,3);
虽然在源代码中看不出来,但上述函数实际上会转化为:
Fraction::set(&fract1, 1, 3);
换言之,指向fract1的一个指针将作为隐藏的第一个参数来传递。在成员函数内部,你可以使用this来访问该参数。
你甚至可以这样定义set函数:
void set(int n,int d){
this->num=n;
this->den=d;
this->normalize();
}
赋值操作符中的this
class Point{
//…
public:
Point &operator=(constPoint &src){
set(src.x,src.y);
return *this;[A11]
}
};
在C++中,赋值操作符函数也必须生成一个值,也就是左侧操作数的值。对于类的操作符函数来说,左侧操作数就是“当前对象”,也就是通过它调用该函数的那个对象。
但是,对象如何返回它本身呢?这里就需要this指针。将提取操作符(*)应用于this,所返回的就是当前对象本身了。return *this;表示的意思是:返回我自己。
[A1]拷贝构造函数
[A2]函数声明中添加了const关键字。注意,该关键字的位置是在起始大括号之前。这种情况下的意思是:函数承诺不会更改任何数据成员,也不会调用除了另一个const函数之外的其它任何函数。
[A3]之所以这样写。第一,他能防止不慎更改数据成员。第二,它允许函数由其它const函数调用。第三,那些承诺不会更改一个Point对象的函数能够调用该函数。
[A4]返回值是Point对象的引用(Point&),而不是一个Point对象。这个返回值可以避免不必要地使用拷贝构造函数。
[A5]在任何赋值操作符函数(=) 定义中,最后一个语句应该是return*this;
[A6]这里用&引用,是因为std::ostream不支持拷贝函数
[A7]替换成你要的输出
[A8]动态分配内存
[A9]强制类型转换
[A10]切记delete
[A11]在任何赋值操作符函数(=) 定义中,最后一个语句应该是return*this;