C++复习
类&对象
类的成员函数
类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。
代码
class Box
{
public:
double length;
double breadth;
double height;
void move();
double getVolume()
{
return length * breadth * height;
}
};
class SmallBox:Box
{
public:
void setLength(double len);
double getLength();
SmallBox(double len); //构造函数
SmallBox(const SmallBox &obj); //拷贝构造函数
~SmallBox(); //析构函数
private:
double length1;
double *ptr;
};
构造函数
当对象被创建时执行
//构造函数
SmallBox::SmallBox(double len): length1(len) //初始化列表
{
//length1 = len; //初始化列表写法等价于这个
cout<<"SmallBox对象被创建了"<<endl;
cout<<"初始化的len:"<<len<<endl;
ptr = new double;
*ptr = len;
}
析构函数
析构函数会在每次删除所创建的对象时执行
//析构函数会在每次删除所创建的对象时执行
SmallBox::~SmallBox()
{
cout<<"析构函数执行了,length:"<<*ptr<<endl;
cout<<"释放内存"<<endl;
delete ptr;
}
拷贝函数
通俗的理解,就是把一个类赋值给另一个类
//如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数
SmallBox::SmallBox(const SmallBox &obj)
{
cout<<"调用拷贝函数并且为指针ptr分配内存"<<endl;
//为指针分配内存
ptr = new double;
*ptr = *obj.ptr;
}
使用:
SmallBox sbox1(7.7);
//此时会调用拷贝函数
SmallBox box2 = sbox1;
SmallBox box3 = sbox1;
友元函数
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,用friend关键字修饰
声明友元函数:
class Box
{
double width;
public:
double length;
friend void printWidth( Box box );
void setWidth( double wid );
};
定义友元函数:
// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width <<endl;
}
声明友元类:
friend class ClassTwo;
内联函数
若一个函数是内联函数,编译器则会把该函数的副本放置在每个调用此函数的地方,当次函数进行任何修改,必须要重新编译,否则将使用旧的代码,定义内联函数,需要在函数名前放置关键字inline,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符
在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符。
this指针
每个对象都可以用this指针来指向自己,指针是所有成员函数的隐含参数,在成员函数内部,它可以用来指向调用对象
友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。
示例代码:
#include <iostream>
using namespace std;
class Box
{
public:
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
}
double Volume()
{
return length * breadth * height;
}
int compare(Box box)
{
return this->Volume() > box.Volume();
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // Declare box1
Box Box2(8.5, 6.0, 2.0); // Declare box2
if(Box1.compare(Box2))
{
cout << "Box2 is smaller than Box1" <<endl;
}
else
{
cout << "Box2 is equal to or larger than Box1" <<endl;
}
return 0;
}
运行结果:
Constructor called.
Constructor called.
Box2 is equal to or larger than Box1
指向类的指针
示例代码:
#include <iostream>
using namespace std;
class Box
{
public:
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
}
double Volume()
{
return length * breadth * height;
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // Declare box1
Box Box2(8.5, 6.0, 2.0); // Declare box2
Box *ptrBox; // Declare pointer to a class.
// 保存第一个对象的地址
ptrBox = &Box1;
// 现在尝试使用成员访问运算符来访问成员
cout << "Volume of Box1: " << ptrBox->Volume() << endl;
// 保存第二个对象的地址
ptrBox = &Box2;
// 现在尝试使用成员访问运算符来访问成员
cout << "Volume of Box2: " << ptrBox->Volume() << endl;
return 0;
}
执行结果
Constructor called.
Constructor called.
Volume of Box1: 5.94
Volume of Box2: 102
类的静态成员
我们可以用static关键字来把类成员定义成静态的,当我们这样做之后,意味着无论我们常见多少对象,静态成员在内存中始终只有一个,或者说,所有的对象公用同一个静态成员。
我们不能把静态成员的初始化放置在类的定义中,可以在类的外部通过 :: 来重新申明静态变量从而对它进行初始化
静态成员变量:
示例代码:
#include <iostream>
using namespace std;
class Box
{
public:
static int objectCount;
Box()
{
// 每次创建对象时增加 1
objectCount++;
}
};
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
int main(void)
{
Box Box1(); // 声明 box1
Box Box2(); // 声明 box2
// 输出对象的总数
cout << "Total objects: " << Box::objectCount << endl;
return 0;
}
执行结果:
Total objects: 2
静态成员函数:
- +静态函数只要使用类名加范围解析运算符 :: 就可以访问
- 静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数
- 静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。
静态成员函数与普通成员函数的区别:
- 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
- 普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针。
示例代码:
#include <iostream>
using namespace std;
class Box
{
public:
static int objectCount;
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// 每次创建对象时增加 1
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
static int getCount()
{
return objectCount;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
int main(void)
{
// 在创建对象之前输出对象的总数
cout << "Inital Stage Count: " << Box::getCount() << endl;
Box Box1(3.3, 1.2, 1.5); // 声明 box1
Box Box2(8.5, 6.0, 2.0); // 声明 box2
// 在创建对象之后输出对象的总数
cout << "Final Stage Count: " << Box::getCount() << endl;
return 0;
}
执行结果:
Inital Stage Count: 0
Constructor called.
Constructor called.
Final Stage Count: 2
继承
- 概念
当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
假设有一个基类 Shape,Rectangle 是它的派生类
// 基类
class Shape
{...}
// 派生类
class Rectangle: public Shape
{...}
-
访问控制和继承
派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。
一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
-
继承类型
我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
-
多继承
通俗的说,即一个子类有多个父类,有多个父类的特性
语法格式:
父类之间用逗号隔开
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,… { <派生类类体> };
示例:
// 基类 Shape class Shape {...} // 基类 PaintCost class PaintCost {...} // 派生类 class Rectangle: public Shape, public PaintCost{...}
重载运算符和重载函数
同名,但是形参不同(参数个数、类型或顺序不同)的函数构成重载
示例:
#include <iostream>
using namespace std;
class printData
{
public:
void print(int i) {
cout << "整数为: " << i << endl;
}
void print(double f) {
cout << "浮点数为: " << f << endl;
}
void print(char c[]) {
cout << "字符串为: " << c << endl;
}
};
int main(void)
{
printData pd;
// 输出整数
pd.print(5);
// 输出浮点数
pd.print(500.263);
// 输出字符串
char c[] = "Hello C++";
pd.print(c);
return 0;
}
执行结果:
整数为: 5
浮点数为: 500.263
字符串为: Hello C++
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。通俗来说,即重新定义运算符的功能
Box operator+(const Box&);
声明加法运算符用于把两个Box对象相加,因此,在使用的时候,需要传入两个Box对象
Box operator+(const Box&, const Box&);
示例:
#include <iostream>
using namespace std;
class Box
{
public:
double getVolume(void)
{
return length * breadth * height;
}
void setLength( double len )
{
length = len;
}
void setBreadth( double bre )
{
breadth = bre;
}
void setHeight( double hei )
{
height = hei;
}
// 重载 + 运算符,用于把两个 Box 对象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 程序的主函数
int main( )
{
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
Box Box3; // 声明 Box3,类型为 Box
double volume = 0.0; // 把体积存储在该变量中
// Box1 详述
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// Box2 详述
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
// Box1 的体积
volume = Box1.getVolume();
cout << "Volume of Box1 : " << volume <<endl;
// Box2 的体积
volume = Box2.getVolume();
cout << "Volume of Box2 : " << volume <<endl;
// 把两个对象相加,得到 Box3
Box3 = Box1 + Box2;
// Box3 的体积
volume = Box3.getVolume();
cout << "Volume of Box3 : " << volume <<endl;
return 0;
}
下面是可重载的运算符列表:
下面是不可重载的运算符列表:
- .:成员访问运算符
- ., ->:成员指针访问运算符
- :::域运算符
- sizeof:长度运算符
- ?::条件运算符
- #: 预处理符号
多态
顾名思义,即多种形态,例如:动物类,下面的子类有猫、狗和羊等到…,猫狗羊就是动物类的多种形态
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
示例代码:
#include <iostream>
using namespace std;
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
int area()
{
cout << "Parent class area :" <<endl;
return 0;
}
};
class Rectangle: public Shape{
public:
Rectangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Rectangle class area :" <<endl;
return (width * height);
}
};
class Triangle: public Shape{
public:
Triangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Triangle class area :" <<endl;
return (width * height / 2);
}
};
// 程序的主函数
int main( )
{
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// 存储矩形的地址
shape = &rec;
// 调用矩形的求面积函数 area
shape->area();
// 存储三角形的地址
shape = &tri;
// 调用三角形的求面积函数 area
shape->area();
return 0;
}
它会产生下列结果:
Parent class area
Parent class area
明细,上面输出的结果有误,导致错误的原因是调用函数 area() 被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接 - 函数调用在程序执行前就准备好了。也被称为早绑定,我们只需要在Shape类中,area()方法前添加virtual关键字即可;
修改后的输出结果:
Rectangle class area
Triangle class area
此时,编译器看的是指针的内容,而不是它的类型
每个子类都有一个函数 area() 的独立实现。这就是多态的一般使用方式。有了多态,您可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。
-
虚函数
虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
-
纯虚函数
您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。
我们可以把基类中的虚函数 area() 改写如下:
class Shape { protected: int width, height; public: Shape( int a=0, int b=0) { width = a; height = b; } // pure virtual function virtual int area() = 0; };
= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。
接口(抽象类)
-
概念
如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 “= 0” 来指定的,如下所示
class Box { public: // 纯虚函数 virtual double getVolume() = 0; private: double length; // 长度 double breadth; // 宽度 double height; // 高度 };
抽象类的子类必须重写所有的纯虚函数
-
抽象类的示例:
#include <iostream> using namespace std; // 基类 class Shape { public: // 提供接口框架的纯虚函数 virtual int getArea() = 0; void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } protected: int width; int height; }; // 派生类 class Rectangle: public Shape { public: int getArea() { return (width * height); } }; class Triangle: public Shape { public: int getArea() { return (width * height)/2; } }; int main(void) { Rectangle Rect; Triangle Tri; Rect.setWidth(5); Rect.setHeight(7); // 输出对象的面积 cout << "Total Rectangle area: " << Rect.getArea() << endl; Tri.setWidth(5); Tri.setHeight(7); // 输出对象的面积 cout << "Total Triangle area: " << Tri.getArea() << endl; return 0; }
它会产生下面的结果:
Total Rectangle area: 35 Total Triangle area: 17
模板
模板是创建泛型类或者函数的蓝图或公式
语法形式:
template <typename type> ret-type func-name(parameter list)
{
// 函数的主体
}
type 是函数所使用的数据类型的占位符名称。这个名称可以在函数定义中使用
示例:
#include <iostream>
#include <string>
using namespace std;
template <typename T>
inline T const& Max (T const& a, T const& b)
{
return a < b ? b:a;
}
int main ()
{
int i = 39;
int j = 20;
cout << "Max(i, j): " << Max(i, j) << endl;
double f1 = 13.5;
double f2 = 20.7;
cout << "Max(f1, f2): " << Max(f1, f2) << endl;
string s1 = "Hello";
string s2 = "World";
cout << "Max(s1, s2): " << Max(s1, s2) << endl;
return 0;
}
语法格式:
template <class type> class class-name {
.
.
.
}
type 是占位符类型名称,可以在类被实例化的时候进行指定。您可以使用一个逗号分隔的列表来定义多个泛型数据类型。
示例:
#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>
using namespace std;
template <class T>
class Stack {
private:
vector<T> elems; // 元素
public:
void push(T const&); // 入栈
void pop(); // 出栈
T top() const; // 返回栈顶元素
bool empty() const{ // 如果为空则返回真。
return elems.empty();
}
};
template <class T>
void Stack<T>::push (T const& elem)
{
// 追加传入元素的副本
elems.push_back(elem);
}
template <class T>
void Stack<T>::pop ()
{
if (elems.empty()) {
throw out_of_range("Stack<>::pop(): empty stack");
}
// 删除最后一个元素
elems.pop_back();
}
template <class T>
T Stack<T>::top () const
{
if (elems.empty()) {
throw out_of_range("Stack<>::top(): empty stack");
}
// 返回最后一个元素的副本
return elems.back();
}
int main()
{
try {
Stack<int> intStack; // int 类型的栈
Stack<string> stringStack; // string 类型的栈
// 操作 int 类型的栈
intStack.push(7);
cout << intStack.top() <<endl;
// 操作 string 类型的栈
stringStack.push("hello");
cout << stringStack.top() << std::endl;
stringStack.pop();
stringStack.pop();
}
catch (exception const& ex) {
cerr << "Exception: " << ex.what() <<endl;
return -1;
}
}
执行结果:
7
hello
Exception: Stack<>::pop(): empty stack