第十章类
简单地说,类就是定义了一个新的类型和一个新作用域。
类由类成员组成。类成员包括属性,字段,成员函数,构造函数,析构函数等组成。
类设计应该遵从抽象封装性
象性指对于类的使用者来说只需知道类接口即可使用类功能。类的具体实现由设计者负责。即使某个功能发生了变更但由于使用者是以接口方式调用类所以用户代码无需做任何修改。
class Screen
{
public:
// 类成员只能声明不允许定义替换成string name("tom") 会产生编译错误,数据成员初始化工作在构造函数中执行
string name;
// 给类定义别名类型成员 index 由于别名要在外部访问所以一定要定义在 public
typedef std::string::size_type index;
// 内部定义的函数,等价于inline
char get() const { return contents[cursor]; }
// 内部声明一个成员函数(无定义),且函数是内联的inline表示在编译时该声明会被替换成定义语句
inline char get(index ht, index wd) const;
// 内部声明一个成员函数(无定义)
index get_cursor() const;
// ...
};
// 定义类 Screen 的成员函数 get 具体实现
char Screen::get(index r, index c) const
{
index row = r * width; // compute the row location
return contents[row + c]; // offset by c to fetch specified character
}
// 定义类 Screen 的成员函数 get_cursor 具体实现,且是内联的
inline Screen::index Screen::get_cursor() const
{
return cursor;
}
一、数据成员定义时不能初始化,比如:
class a
{
int i;
int j = 1; //错误,不能初始化值
}
二、类的构造函数对自身数据成员包括初始化和赋值,初始化:i(1),j(1),赋值是在构造函数体内的显示给值,对有类型数据来说二者效果一样,而有些类型数据只能做初始化而不允许赋值。例如:
class a
{
private:
int i;
int j;
const int k;
public:
a(int d):i(1),j(2),k(3)
{
// 函数体叫赋值操作而不是初始化,本例中K在这里不再允许赋值
}
}
三、和函数一样,可以先声明再定义,未定义之前声明的类叫不完全类型,这种声明叫向前声明。它的含义是表示有这么一个类,但成员及成员类型未知,例如:
class a; // 声明了一个叫a的不完全类型
四、类定义以分号结束,可以在定义类的代码后直接定义类对象并以分号结束例如:
class a
{
//somthing
} cla,clb;
表示定义了两个变量名分别为cla,clb的类型为类a的对象;
五、 非静态类中有个叫 this 的指针指向当前类本身,该指针是 *const 的,意味着不能改变这个指针的指向。就我的理解就是这个指针的值是不能改变的。其实后来一看这个指向说的也挺好的。
在类的有返回方法中,如果原形为: const &a funt(){ return *this; } 意味着既不能改变返回的this的指向,也不能改变返回this所指向的成员的数据。
六、针对以上出现的问题,可以对返回的this指针对象方法做 const 重载,如:
class a
{
const &a funt() const { return *this; }; // 这个版本方法的返回类不能修改成员数据
&a funt() const {return *this; }; // 这个版本方法的返回类可以修改成员数据
}
在程序中调用方法时如果返回的类没有修改操作系统会调用const版本,否则调用非const版本。
七、类一般要定义一个默认构造函数,如果没有默认构造函数有时候会出问题,例如假设类B无默认构造函数(无参的构造函数),在类A的构造函数中又没有显示初始化这时就会处错。
class A
{
B p;
A(){}; //如果定义A对象就会出错,因为类B没有无参的构造函数
}
八、类通过构造函数一般会做类类型转换,例如类A有接收一个int类型参数的构造函数则:
A list[] = {1,2,3} 等价于 A list[] = {A(1),A(2),A(3)}
也就是可以通过只提供构造函数的参数来转换为具体类对象(只有一个参数的构造函数才能做转换,多个参数的构造函数无法做转换)
可以通过在构造函数前增加关键字 explicit 来阻止这类转换。
九、类可以有静态成员,类的静态数据成员只能在定义体外定义(这个地方我认为类内部可以的,只要定义的时候就初始化),且定义时需要同时给予初始化。因为静态数据成员不能在构造函数中初始化,c++类也不提供静态构造函数。示例代码如下:
class A{}
int A::sti = 2; // 表示对类A的静态int型数据成员sti定义且初始化为2.
有一种情况例外,类的静态常量数据成员不可以在定义体外定义和初始化,而必须要在类定义体内定义及初始化。 class a{ private: static const stj = 2; }
十、不同于C#对静态成员只能通过类名称来使用,c++静态成员不但可以通过类名称来使用,也可以通过具体对象来使用。
十一、 有一种特殊情况,对于整值或枚举型的static const变量,可以直接在类内声明定义。如:
class A
{
public:
static const int i = 3;
int p[i];
};
这个i就在类内定义给值了,因为下面p变量需要它,但即使这样也需要在类外把它重新定义一下:
const int A::i;
定义的时候,不要再把初始化的值写上。
关于上面的第11条有写没有说清楚。
10.2.4 静态成员
如果一个变量时类的一部分, 但却不是该类的各个对象的一部分,它就被称为是一个static静态成员。static 成员只有唯一的一份副本,而不像常规的非static成员那样在每个对象里各有一份副本。一次类似,一个需要访问的类成员,然而却并不需要针对特定对象去调用的函数,也被称为一个static成员函数。
这部分编程中范的错误:
//cout << n +'/' + d <<endl;//这样的输出错误, 因为会转化成ascii而输出
cout<< this << ",F("<< n << ','<< d << ')'<< endl;
class Date
{
int d, m, y;
static Date default_date;
public:
Date(int dd = 0, int mm = 0, int yy = 0);
static void set_default(int ,int, int); //这个地方的static,成员函数也是类的一部分。
};
void Date::set_default(int d,int m, int y)
{
Date::default_date = Date(d, m, y);
}
//在使用静态成员之前必须将静态成员初始化
Date Date::default_date(16,12,1770);
10.2.5 对象的复制
按照默认规定,类对象可以复制。特别是可以用同一个类对象的复制对该类的其他对象进行初始化。
10.2.6 成员函数
int day() const{return d;} ,
在函数声明的后面出现的const,它指明这些函数不会修改Date的状态。
10.2.7 自引用
对于一些更新函数,可以让它们返回一个到更新对象的引用。
void f(Date &d)
{
d.add_day(2).add_month(1).add_year(2);
}
Date& Date::add_year(int n)
{
//...
return *this;
}
偶尔有这种情况
下面这部分是达内的课程笔记
静态成员函数_运算符重载_临时对象
càmallocàfree cppànewàdelete
thisà当前对象的地址
mutableà可变的成员变量。即使对象是常量,也可修改,表面上不用修改,但是实际是需要修改的。
const 加在不同位置,含义不一样。const加在函数名后面,表示对对象只能做只读操作。
C++ 使用new申请动态内存,但是尽量不要用动态内存。
而不使用malloc 申请内存,C++是强制内存检查,malloc 得到的是void* 类型。所以需要强制转换static_cast<F*>
构造函数可以重载,析构函数是无参的,所以不能重载,我刚试了一下,而且析构函数不能有参数。在成员函数中末尾有const或者没有const都构成重载。
在函数里面,形参尽量用const,尽量用引用。输入输出流作为形参的时候不要加const,会改变。
在类内部的函数定义
如果一个函数在类定义的内部定义,而不是仅仅在这里声明,它就被作为一个内联函数。也就是说在类内部定义的成员函数应该是小的、频繁使用的函数。
运算符函数的名字是由关键词operator+对应的运算符构成的;例如operator<<。使用运算符不过是显示调用运算符函数的一种简写形式。
操作符重载可以分为两部分:“操作符”和“重载”。说到重载想必都不陌生了吧,这是一种编译时多态,重载实际上可以分为函数重载和操作符重载。运算符重载和函数重载的不同之处在于操作符重载重载的一定是操作符。我们不妨先直观的看一下所谓的操作符重载:
我们看到操作符“+”完成float和int两种类型的加法计算,这就是操作符重载了。这些内置类型的操作符重载已经实现过了,但是如果现在我们自己写过的类也要实现实现类似的加法运算,怎么办呢??比如现在现在有这样一个点类point,要实现两个点的相加,结果是横纵坐标都要相加,这时候就需要我们自己写一个操作符重载函数了。
以下摘自:http://www.cnblogs.com/CaiNiaoZJ/archive/2011/08/12/2136598.html
运算符重载是对已有的运算符赋予多重含义,使同一个运算符作用域不同类型的数据导致不同行为的发生。比如
#include <iostream>
using namespace std;
int main()
{
int a = 2 , b = 3;
float c = 2.1f , d = 1.2f;
cout<<"a + b = "<<a+b<<endl;
cout<<"c + d = "<<c+d<<endl;
return 0;
}
在这个程序里"+"既完成两个整形数的加法运算,又完成了双精度型的加法运算。为什么同一个运算符"+"可以用于完成不同类型的数据的加法运算?这是因为C++针对预定义基本数据类型已经对"+"运算符做了适当的重载。在编译程序编译不同类型数据的加法表达式时,会自动调用相应类型的加法运算符重载函数。但是C++中所提供的预定义的基本数据类型毕竟是有限的,在解决一些实际的问题时,往往需要用户自定义数据类型。比如高中数学里所提到的复数 . 假如我们建立两个复数,并用"+"运算符让它们直接相加:
Complex com1(10,10),com2(20,20),sum;
sum=com1+com2;
那么会提示没有与这些操作数匹配的 "+" 运算符的错误。这是因为Complex类类型不是预定义类型,系统没用对该类型的数据进行加法运算符函数的重载。C++就为运算符重载提供了一种方法,即运算符重载函数。其函数名字规定为operator后紧跟重载运算符。比如:operator+(),operator*()等。现在我们给上述程序声明一个加法运算符的重载函数用于完成复数的加法运算:
class Complex //复数类
{
public:
double real;//实数
double imag;//虚数
Complex(double real=0,double imag=0)
{
this->real=real;
this->imag=imag;
}
};
Complex operator+(Complex com1,Complex com2)//运算符重载函数
{
return Complex(com1.real+com2.real,com1.imag+com2.imag);
}
int main()
{
Complex com1(10,10),com2(20,20);
sum=com1+com2;//或sum=operator+(com1,com2)
std::cout<<"sum的实数部分为"<<sum.real<<std::endl;
std::cout<<"sum的虚数部分为"<<sum.imag<<"i"<<std::endl;
return0;
}
psàconst 和引用必须初始化。
在上述示例代码中,调用运算符重载函数时,也可以以operator+(com1,com2)的形式来调用,实际上com1+com2在程序解释时也是转化成前者一样的形式。com1+com2的意思就是com1.operator+(com2)。
但是直接用com1+com2的形式更加符合人的书写习惯。
2、述示例中的运算符重载函数是不属于任何的类,是全局的函数。因为在Complex类中的数据成员是公有的性质,所以运算符重载函数可以访问。但如果定义为私有的呢,那该怎么办。其实,在实际的运算符重载函数声明当中,要不定义其为要操作类的成员函数或类的友元函数。
(1) 运算符重载函数作为类的友元函数的形式:
class 类名
{
friend 返回类型 operator运算符(形参表);
}
类外定义格式:
返回类型 operator运算符(参数表)
{
函数体
}
友元函数重载双目运算符(有两个操作数,通常在运算符的左右两则),参数表中的个数为两个。若是重载单目运算符(只有一个操作数),则参数表中只有一参数。
i、友元函数重载双目运算符(+):
#include "stdafx.h"
#include <iostream>
class Complex //复数类
{
private://私有
double real;//实数
double imag;//虚数
public:
Complex(double real=0,double imag=0)
{
this->real=real;
this->imag=imag;
}
friend Complex operator+(Complex com1,Complex com2);//友元函数重载双目运算符+ , 如果不加friend就会报错
void showSum();
};
Complex operator+(Complex com1,Complex com2)//友元运算符重载函数
{
return Complex(com1.real+com2.real,com1.imag+com2.imag);
}
void Complex::showSum()
{
std::cout<<real;
if(imag>0)
std::cout<<"+";
if(imag!=0)
std::cout<<imag<<"i"<<std::endl;
}
int main()
{
Complex com1(10,10),com2(20,-20),sum;
sum=com1+com2;//或sum=operator+(com1,com2)
sum.showSum();//输出复数相加结果
return0;
}
ii、友元函数重载单目运算符(++):
class Point//坐标类
{
private:
int x;
int y;
public:
Point(int x,int y)
{
this->x=x;
this->y=y;
}
friend void operator++(Point& point);//友元函数重载单目运算符++
void showPoint();
};
void operator++(Point& point)//友元运算符重载函数
{
++point.x;
++point.y;
}
void Point::showPoint()
{
std::cout<<"("<<x<<","<<y<<")"<<std::endl;
}
int main()
{
Point point(10,10);
++point;//或operator++(point)
point.showPoint();//输出坐标值
return0;
}
运算符重载函数可以返回任何类型,甚至是void,但通常返回类型都与它所操作的类类型一样,这样可以使运算符使用在复杂的表达式中。比如把上述双目运算符重载函数示例代码中main()主函数里的com1+com2改为com1+com2+com2,那么结果又会不一样了。像赋值运算符=、下标运算符[]、函数调用运算符()等是不能被定义为友元运算符重载函数。同一个运算符可以定义多个运算符重载函数来进行不同的操作。
(2)运算符重载函数作为类的成员函数的形式:
class 类名
{
返回类型 operator 运算符(形参表);
}
类外定义格式:
返回类型 类名:: operator 运算符(形参表)
{
函数体;
}
上面或者是可以写为p1.operator++();
在运算符重载运用时应该注意以下几个问题:
(1)C++中只能对已有的C++运算符进行重载,不允许用户自己定义新的运算符;
(2)C++中绝大部分的运算符可重载,除了成员访问运算符’.’,成员指针访问运算符.*,作用域运算符::,长度运算符sizeof以及条件运算符?:;
(3)重载后不能改变运算符的操作对象(操作数)的个数。如:"+"是实现两个操作数的运算符,重载后仍然为双目运算符;
(4)重载不能改变运算符原有的优先级;
(5)重载不能改变运算符原有结合的特性。比如:z=x/y*a,执行时是先做左结合的运算x/y,重载后也是如此,不会变成先做右结合y*a;
(6)运算符重载不能全部是C++中预定义的基本数据,这样做的目的是为了防止用户修改用于基本类型数据的运算符性质;
(7)从上述的示例中可以看到双目运算符可以被重载为友元函数也可以重载为成员函数,但有一种情况,只能使用友元函数,是什么情况呢?我举个例子:
如果我们把上述main()主函数实现部分里的total=com1+5改为total=5+com1;那么程序就会报错(没有与这些操作数匹配的 "+" 运算符),因为左操作数5不是该复数类的对象,不能调用相应的成员函数Complex operator+(int x),所以编译错误。但如果我们定义一下两个友元函数就能解决上述的问题:
friend Complex operator+(Complex com1,int x);
friend Complex operator+(int x,Complex com1);
ps: c++ 程序设计语言 特别版à 236
如果某个运算符函数想接受某个内部类型作为第一个参数,那么它自然就不可能是成员函数了。 举个例子,考虑一个复数加到2上: 只要有适当声明的成员函数,aa+2可以解释为aa.operateor+(2),但是2+aa却不能,因为int没有定义+去表示2+.operateor+(aa)。
枚举也是用户定义类型,因此可以为它们定义运算符。
11.3.3 初始化
对于上面的complex类, 想要用标量来对Complex变量做初始化和赋值, 我们就需要从标量到Complex的转换。