C++类的初级使用

定义一个类:

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;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值