C++类的初级使用

发布于:2021-09-23 07:20:27

定义一个类:

Class 类名{


?????? 声明


};


声明中可包含数据声明、函数声明。成员默认情况下都是private的,换言之,它们不能被访问。另外需要注意的是,类的声明最末尾需要有一个分号“;”。


在C++中,struct和class关键字是等价的,唯一的区别就是struct的成员默认为public。这两个关键字在C++中实际都会创建类。这同时意味着,术语“类”和关键字class并不是严格对应的。


C++之所以保留struct定义,完全是为了保持与C的向下兼容性。在面向对象程序设计中,只有在有充分理由的前提下,才考虑让一个成员成为public成员。


成员函数定义:

Type class_name::function_name(argument_list){


?????? statements



}



#include
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 "<
pt2.set(-15,-2);
cout<<"pt2 is "<}
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
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 "<
cout<<"pt3 is "<}
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<

?????? 你会发现系统并没有打出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
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"<}
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< 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<

????????再假设你忘了为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
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 cout<<"Enteritem #"< cin>>p[i];
sum+=p[i];
}
cout<<"Here arethe items: ";
for(int i=0;i cout< }
cout< cout<<"The totalis: "< cout<<"Theaverage is :"<(sum)[A9] /n< 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;

相关推荐