SMU-C++面向对象开发
2019-09-11 15:26:47 15 举报
AI智能生成
北京大学C++课程前五周内容整理,需要的可以节约时间
作者其他创作
大纲/内容
第四周
运算符重载
运算符
只能用于基本的数据类型:
整型、 实型, 字符型, 逻辑型...
整型、 实型, 字符型, 逻辑型...
缺点:不能对复数直接进行+-
运算符重载
• 对已有的运算符赋予多重的含义
• 使同一运算符作用于不同类型的数据时 -> 不同类型的行为
• 使同一运算符作用于不同类型的数据时 -> 不同类型的行为
• 把含 运算符的表达式 ->对 运算符函数 的调用
• 把 运算符的操作数 -> 运算符函数的 参数
• 运算符被多次重载时, 根据 实参的类型 决定调用哪个运算符函数
• 运算符可以被重载成普通函数
• 也可以被重载成类的成员函数
• 把 运算符的操作数 -> 运算符函数的 参数
• 运算符被多次重载时, 根据 实参的类型 决定调用哪个运算符函数
• 运算符可以被重载成普通函数
• 也可以被重载成类的成员函数
返回值类型 operator 运算符(形参表)
用 Operator 来完成+ -号的代换
用 Operator 来完成+ -号的代换
• 重载为普通函数时, 参数个数为运算符目数
Complex operator+ (const Complex & a, const Complex & b)
{
return Complex( a.real+b.real, a.imaginary+b.imaginary);
} // “类名(参数表)” 就代表一个对象
{
return Complex( a.real+b.real, a.imaginary+b.imaginary);
} // “类名(参数表)” 就代表一个对象
• 重载为成员函数时, 参数个数为运算符目数减一
class Complex {
public:
Complex( double r= 0.0, double m = 0.0 ):
real(r), imaginary(m) { } // constructor
Complex operator+ ( const Complex & ); // addition
Complex operator- ( const Complex & ); // subtraction
private:
double real; // real part
double imaginary; // imaginary part
};
public:
Complex( double r= 0.0, double m = 0.0 ):
real(r), imaginary(m) { } // constructor
Complex operator+ ( const Complex & ); // addition
Complex operator- ( const Complex & ); // subtraction
private:
double real; // real part
double imaginary; // imaginary part
};
赋值运算符‘=’的重载
赋值运算符 “=” 只能重载为 成员函数(非普通函数)
.
且返回值不能是void类型
.
且返回值不能是void类型
int main(){
String s;
s = “Good Luck,” ;
cout << s.c_str() << endl;
// String s2 = “hello!”; //这条语句要是不注释掉就会出错
s = "Shenzhou 8!";
cout << s.c_str() << endl;
return 0;
}
String s;
s = “Good Luck,” ;
cout << s.c_str() << endl;
// String s2 = “hello!”; //这条语句要是不注释掉就会出错
s = "Shenzhou 8!";
cout << s.c_str() << endl;
return 0;
}
浅复制/浅拷贝
MyString S1, S2;
S1 = “this”;
S2 = “that”;
S1 = S2;
S1 = “this”;
S2 = “that”;
S1 = S2;
s1.str = s2.str 两个指针共同指向同一地址
缺点:有一个地址没有指针指向,变成内存垃圾;
两个指针删除后,内存被释放两次,造成错误。
两个指针删除后,内存被释放两次,造成错误。
深复制/深拷贝
将一个对象中指针变量指向的内容,
->复制到另一个对象中指针成员对象指向的地方
(仍为两个指针,两个指针指向两个相同的被复制内容)
->复制到另一个对象中指针成员对象指向的地方
(仍为两个指针,两个指针指向两个相同的被复制内容)
String & operator = (const String & s) { //使用const以确保s指针不会被修改
if(str) delete [] str;
str = new char[strlen(s.str)+1];
strcpy(str, s.str); //复制指针地址所指向的内容,到新的指针当中
return * this;
}
if(str) delete [] str;
str = new char[strlen(s.str)+1];
strcpy(str, s.str); //复制指针地址所指向的内容,到新的指针当中
return * this;
}
string 与 string &
运算符重载时, 好的风格 -- 尽量保留运算符原本的特性
stromh &仅为一次引用
: (a=b)=c; //会修改a的值,其余为引用
(a.operator=(b)).operator=(c);
运算符重载为友元函数
既要是普通函数,完成普通的数加减;
又要是类成员函数,进行复数的加减;
又要是类成员函数,进行复数的加减;
可变长整型数组(运算符重载实例)
需要自己跑一遍!!!
流插入运算符和流提取运算符的重载(教材P218)
没看
自加、自减运算符的重载
第五周
继承与派生
继承:在定义一个新的类B时,如果该类与某
个已有的类A相似(指的是B拥有A的全部特点),
那么就可以把A作为一个基类,而把B作为基
类的一个派生类(也称子类)。
个已有的类A相似(指的是B拥有A的全部特点),
那么就可以把A作为一个基类,而把B作为基
类的一个派生类(也称子类)。
派生类是通过对基类进行修改和扩充得到
的。在派生类中,可以扩充新的成员变量
和成员函数
的。在派生类中,可以扩充新的成员变量
和成员函数
派生类一经定义后,可以独立使用,不依
赖于基类。
赖于基类。
在派生类的各个成员函数中,不能访问
基类中的private成员
基类中的private成员
派生类的写法:
class 派生类名:public 基类名
{
};
class 派生类名:public 基类名
{
};
void PrintInfo() {
CStudent::PrintInfo(); //调用基类的PrintInfo
cout << “Department:” << department <<endl;
}
CStudent::PrintInfo(); //调用基类的PrintInfo
cout << “Department:” << department <<endl;
}
派生类对象的内存空间
派生类对象的体积,等于基类对象的体积,再加上派
生类对象自己的成员变量的体积。在派生类对象中,包
含着基类对象,而且基类对象的存储位置位于派生类对
象新增的成员变量之前。
生类对象自己的成员变量的体积。在派生类对象中,包
含着基类对象,而且基类对象的存储位置位于派生类对
象新增的成员变量之前。
继承关系与复合关系
继承:“是”关系。
– 基类 A,B是基类A的派生类。
– 逻辑上要求:“一个B对象也是一个A对象”。
– 基类 A,B是基类A的派生类。
– 逻辑上要求:“一个B对象也是一个A对象”。
子集与母集的关系
复合:“有”关系。
– 类C中“有”成员变量k,k是类D的对象,则C和D是复合关系
– 一般逻辑上要求:“D对象是C对象的固有属性或组成部 、分”。
– 类C中“有”成员变量k,k是类D的对象,则C和D是复合关系
– 一般逻辑上要求:“D对象是C对象的固有属性或组成部 、分”。
点与圆的关系:
“点”是一类,“圆”不是“点”,但是“圆”里面有圆心(“点”)
所以”圆“里面有点,包含一个”点“对象。
“点”是一类,“圆”不是“点”,但是“圆”里面有圆心(“点”)
所以”圆“里面有点,包含一个”点“对象。
切忌A类与B类混合:
循环定义:
class CDog;
class CMaster {
CDog dogs[10];
};
class CDog {
CMaster m;
};
循环定义:
class CDog;
class CMaster {
CDog dogs[10];
};
class CDog {
CMaster m;
};
复合类的写法:
使用指针指向,叫做“知道”关系
一个类的成员是另一个类的指针
使用指针指向,叫做“知道”关系
一个类的成员是另一个类的指针
正确的写法:
为“狗”类设一个“业主”类的对象指针;
为“业主”类设一个“狗”类的对象指针数组。
为“狗”类设一个“业主”类的对象指针;
为“业主”类设一个“狗”类的对象指针数组。
基类/派生类同名成员
Protected关键字
Protected关键字
同名成员
不推荐基类和派生类定义同名成员变量
void derived::access()
{
j = 5; //error
i = 5; //引用的是派生类的 i
base::i = 5; //引用的是基类的 i
func(); //派生类的
base::func(); //基类的
}
{
j = 5; //error
i = 5; //引用的是派生类的 i
base::i = 5; //引用的是基类的 i
func(); //派生类的
base::func(); //基类的
}
Protected 访问范围说明符
基类的protected成员: 可以被下列函数访问
• 基类的成员函数
• 基类的友员函数
• 派生类的成员函数可以访问当前对象的基类的保护成员
• 基类的成员函数
• 基类的友员函数
• 派生类的成员函数可以访问当前对象的基类的保护成员
class Father {
private: int nPrivate; //私有成员
public: int nPublic; //公有成员
protected: int nProtected; // 保护成员
};
class Son : public Father {
void AccessFather () {
nPublic = 1; // ok;
nPrivate = 1; // wrong
nProtected = 1; // OK, 访问从基类继承的protected成员
Son f;
f.nProtected = 1; //wrong, f不是当前对象
}
};
private: int nPrivate; //私有成员
public: int nPublic; //公有成员
protected: int nProtected; // 保护成员
};
class Son : public Father {
void AccessFather () {
nPublic = 1; // ok;
nPrivate = 1; // wrong
nProtected = 1; // OK, 访问从基类继承的protected成员
Son f;
f.nProtected = 1; //wrong, f不是当前对象
}
};
int main(){
Father f;
Son s;
f.nPublic = 1; // Ok
s.nPublic = 1; // Ok
f.nProtected = 1; // error
f.nPrivate = 1; // error
s.nProtected = 1; //error
s.nPrivate = 1; // error
return 0;
}
Father f;
Son s;
f.nPublic = 1; // Ok
s.nPublic = 1; // Ok
f.nProtected = 1; // error
f.nPrivate = 1; // error
s.nProtected = 1; //error
s.nPrivate = 1; // error
return 0;
}
派生类的构造函数
构造函数名(形参表): 基类名(基类构造函数实参表)
{
}
{
}
class Bug { //构造派生类函数
private :
int nLegs; int nColor;
public:
int nType;
Bug (int legs, int color);
void PrintBug () { };
};
class FlyBug: public Bug { // FlyBug是Bug的派生类
int nWings;
public:
FlyBug(int legs, int color, int wings);
};
private :
int nLegs; int nColor;
public:
int nType;
Bug (int legs, int color);
void PrintBug () { };
};
class FlyBug: public Bug { // FlyBug是Bug的派生类
int nWings;
public:
FlyBug(int legs, int color, int wings);
};
Bug::Bug( int legs, int color) {
nLegs = legs;
nColor = color;
}
//错误的FlyBug构造函数:
FlyBug::FlyBug (int legs, int color, int wings) {
nLegs = legs; // 不能访问
nColor = color; // 不能访问
nType = 1; // ok
nWings = wings;
}
//正确的FlyBug构造函数:
FlyBug::FlyBug (int legs, int color, int wings):Bug(legs, color) {
nWings = wings;
}
nLegs = legs;
nColor = color;
}
//错误的FlyBug构造函数:
FlyBug::FlyBug (int legs, int color, int wings) {
nLegs = legs; // 不能访问
nColor = color; // 不能访问
nType = 1; // ok
nWings = wings;
}
//正确的FlyBug构造函数:
FlyBug::FlyBug (int legs, int color, int wings):Bug(legs, color) {
nWings = wings;
}
创建 派生类的对象
• 需要调用 基类的构造函数:
初始化派生类对象中从基类继承的成员
• 在执行一个派生类的构造函数之前,
总是先执行基类的构造函数
初始化派生类对象中从基类继承的成员
• 在执行一个派生类的构造函数之前,
总是先执行基类的构造函数
调用基类构造函数的两种方式
• 显式方式:
派生类的构造函数中 基类的构造函数提供参数
derived::derived(arg_derived-list):base(arg_base-list)
• 隐式方式:
派生类的构造函数中, 省略基类构造函数时
派生类的构造函数, 自动调用基类的默认构造函数
派生类的构造函数中 基类的构造函数提供参数
derived::derived(arg_derived-list):base(arg_base-list)
• 隐式方式:
派生类的构造函数中, 省略基类构造函数时
派生类的构造函数, 自动调用基类的默认构造函数
检测:
class Base {
public:
int n;
Base(int i):n(i)
{ cout << "Base " << n << " constructed" << endl; }
~Base()
{ cout << "Base " << n << " destructed" << endl; }
};
class Derived:public Base {
public:
Derived(int i):Base(i)
{ cout << "Derived constructed" << endl; }
~Derived()
{ cout << "Derived destructed" << endl; }
};
int main() { Derived Obj(3); return 0; }
class Base {
public:
int n;
Base(int i):n(i)
{ cout << "Base " << n << " constructed" << endl; }
~Base()
{ cout << "Base " << n << " destructed" << endl; }
};
class Derived:public Base {
public:
Derived(int i):Base(i)
{ cout << "Derived constructed" << endl; }
~Derived()
{ cout << "Derived destructed" << endl; }
};
int main() { Derived Obj(3); return 0; }
创建 派生类的对象 时, 执行 派生类的构造函数 之前:
• 调用 基类 的构造函数
-> 初始化派生类对象中从基类继承的成员
• 调用 成员对象类 的构造函数
->初始化派生类对象中成员对象
执行完 派生类的析构函数 后:
• 调用 成员对象类 的析构函数
• 调用 基类 的析构函数
->析构函数的调用顺序与构造函数的调用顺序相反
• 调用 基类 的构造函数
-> 初始化派生类对象中从基类继承的成员
• 调用 成员对象类 的构造函数
->初始化派生类对象中成员对象
执行完 派生类的析构函数 后:
• 调用 成员对象类 的析构函数
• 调用 基类 的析构函数
->析构函数的调用顺序与构造函数的调用顺序相反
public继承的赋值兼容规则
public继承的赋值兼容规则
class base { };
class derived : public base { };
base b;
derived d;
class derived : public base { };
base b;
derived d;
1) 派生类的对象可以赋值给基类对象
b = d;
2) 派生类对象可以初始化基类引用
base & br = d;
3) 派生类对象的地址可以赋值给基类指针
base * pb = & d;
b = d;
2) 派生类对象可以初始化基类引用
base & br = d;
3) 派生类对象的地址可以赋值给基类指针
base * pb = & d;
如果派生方式是 private或protected,则上述三条不可行
第二周
位运算
异或 ^
a = a^b
b = b^a
a = a^b
b = b^a
a = a^b
可以实现a,b值的交换
左移 <<
9 <<4
数字9左移四位,
相当于乘2^n
相当于乘2^n
右移 >>
-2>>4 = -1 (小里取整)
右移的若没有符号位,直接补零;
有符号位则符号位不变,补进来的数为符号位数字(11011>>11110)
有符号位则符号位不变,补进来的数为符号位数字(11011>>11110)
原数除2^n,往小里取整
右移的若没有符号位,直接补零;
有符号位则符号位不变,补进来的数为符号位数字(11011>>11110)
有符号位则符号位不变,补进来的数为符号位数字(11011>>11110)
引用
& r1 = a;
& r2 = r1; //意思是r2也指向a
r1 = b; //给r1,r2,a均赋值为b
& r2 = r1; //意思是r2也指向a
r1 = b; //给r1,r2,a均赋值为b
void swap (& a, & b); //直接调用地址中的值,返回值也会是地址值的变化
const int &r = n; //常引用,r的值不能改变,只能改变n的值
Const
常量指针不能赋值给非常量指针;反之可以
动态内存分配 Page109
new运算符
1. 分配一个变量
int* P = new int; //注意P的类型是指针
P = new T; //T是任意类型名,P是类型为T *的指针 2. 分配一个数组
P = new T [N];
p [0] = 1;
delete [ ] p;
动态存储空间,使用结束要释放空间
delete P;
delete方式上数组与变量不同
内联函数 Page66
inline int Max (int a, int b);
将整个调用函数都插入到调用语句处
(避免每次调用)
函数重载 p67
函数名字相同,而参数个数或参数类型不同
int Max (double f1, double f2) { };
int Max (int n1, int n2) { };
int Max (int n1,int n2, int n3) { };
函数根究参数类型自动选择调用哪个
缺省参数 p61
定义函数时让最右边的连续若干个参数有缺省值
提高程序可扩充性
void func( int x1, int x2 = 2, int x3 = 3) { }
func(10 ) ; //等效于 func(10,2,3)
func(10,8) ; //等效于 func(10,8,3)
func(10, , 8) ; //不行,只能最右边的连续若干个参数缺省
第三周
构造函数 p179
名字与类名相同,可以有参数,不能有返回值(void也不行)
作用是对对象进行初始化,如给成员变量赋初值
对象生成时一定会被构造函数初始化
如果定义类时没写构造函数,则编译器生成一个默认的无参数 的构造函数
•默认构造函数无参数,不做任何操作
class Complex {
private :
double real, imag;
public:
void Set( double r, double i);
}; //编译器自动生成默认构造函数
Complex c1; //默认构造函数被调用
Complex * pc = new Complex; //默认构造函数被调用
Complex c1; // error, 缺少构造函数的参数
Complex * pc = new Complex; // error, 没有参数
Complex c1(2); // OK
Complex c1(2,4), c2(3,5);
Complex * pc = new Complex(3,4);
复制构造函数 p183
只有一个参数,即对同类对象的引用
class Complex {
public :
double real,imag;
Complex(){ }
//第二个情况为复制构造函数
Complex( const Complex & c ) {
real = c.real;
imag = c.imag;
cout << “Copy Constructor called”;
}
};
Complex c1;
Complex c2(c1);//调用自己定义的复制构造函数,输出 Copy Constructor called
public :
double real,imag;
Complex(){ }
//第二个情况为复制构造函数
Complex( const Complex & c ) {
real = c.real;
imag = c.imag;
cout << “Copy Constructor called”;
}
};
Complex c1;
Complex c2(c1);//调用自己定义的复制构造函数,输出 Copy Constructor called
复制构造函数起作用的三种情况
当用一个对象去初始化同类的另一个对象时。
如果某函数有一个参数是类 A 的对象,
那么该函数被调用时,类A的复制构造函数将被调用。
那么该函数被调用时,类A的复制构造函数将被调用。
如果函数的返回值是类A的对象时,则函数返回时,
A的复制构造函数被调用
A的复制构造函数被调用
Copy constructor called
类型转换构造函数
实现类型的自动转换
只有一个参数
不是复制构造函数
建立一个 临时对象 / 临时变量
注意“=”用作初始化,而不一定只能是赋值
析构函数(Destructor)
对比构造函数(初始化对象使用 )
成员函数一种,名字与类名相同
前面加‘~’;没有参数和返回值
一个类最多只有一个析构函数
前面加‘~’;没有参数和返回值
一个类最多只有一个析构函数
class String{
private :
char * p;
public:
String () {
p = new char[10];
}
~ String ();
};
String ::~ String() {
delete [] p;
}
private :
char * p;
public:
String () {
p = new char[10];
}
~ String ();
};
String ::~ String() {
delete [] p;
}
静态成员变量和静态成员函数
在说明前面加了static关键字的成员
普通成员变量每个对象有各自的一份,而静态成员变
量一共就一份,为所有对象共享(相当于全局变量)
量一共就一份,为所有对象共享(相当于全局变量)
必须在定义类的文件中对静态成员变量进行一次说明
//或初始化
//或初始化
在静态成员函数中,不能访问非静态成员变量,
也不能调用非静态成员函数。
也不能调用非静态成员函数。
class CRectangle
{
private:
int w, h;
static int nTotalArea;
static int nTotalNumber;
public:
CRectangle(int w_,int h_);
~CRectangle();
static void PrintTotal();
};
private:
int w, h;
static int nTotalArea;
static int nTotalNumber;
public:
CRectangle(int w_,int h_);
~CRectangle();
static void PrintTotal();
};
普通成员函数必须具体作用于某个对象,而静态成员
函数并不具体作用与某个对象
函数并不具体作用与某个对象
不需要通过对象就能访问。
1) 类名::成员名
CRectangle::PrintTotal();
2) 对象名.成员名
CRectangle r; r.PrintTotal();
3) 指针->成员名
CRectangle * p = &r; p->PrintTotal();
4) 引用.成员名
CRectangle & ref = r; int n = ref.nTotalNumber;
CRectangle::PrintTotal();
2) 对象名.成员名
CRectangle r; r.PrintTotal();
3) 指针->成员名
CRectangle * p = &r; p->PrintTotal();
4) 引用.成员名
CRectangle & ref = r; int n = ref.nTotalNumber;
成员对象和封闭类
成员对象:一个类的成员变量是另一个类的对象
封闭类(enclosing):包含成员对象 的
CTyre(int r, int w):radius(r), width(w) { } //初始化后将半径宽度放到变量中
class CCar { //汽车类 “封闭类”
private:
int price; //价格
CTyre tyre;
CEngine engine;
public:
CCar(int p, int tr, int tw);
};
CCar::CCar (int p, int tr, int w):price(p), tyre(tr, w){
private:
int price; //价格
CTyre tyre;
CEngine engine;
public:
CCar(int p, int tr, int tw);
};
CCar::CCar (int p, int tr, int w):price(p), tyre(tr, w){
};
int main(){
CCar car(20000,17,225);
return 0;
}
int main(){
CCar car(20000,17,225);
return 0;
}
封闭类构造函数的初始化列表
类名::构造函数(参数表):成员变量1(参数表), 成员变量2(参数表), …
调用顺序:
当封闭类对象生成时,
• S1: 执行所有成员对象 的构造函数
• S1: 执行所有成员对象 的构造函数
• S2: 执行 封闭类 的构造函数
成员对象的构造函数调用顺序
成员对象的构造函数调用顺序
• 和成员对象在类中的说明顺序一致
• 与在成员初始化列表中出现的顺序无关
当封闭类的对象消亡时,
• S1: 先执行 封闭类 的析构函数
当封闭类的对象消亡时,
• S1: 先执行 封闭类 的析构函数
• S2: 执行 成员对象 的析构函数
析构函数顺序和构造函数的调用顺序相反
析构函数顺序和构造函数的调用顺序相反
先调用成员变量,然后调用类,析构时相反
友元函数(friend)
一个类的友元函数可以访问该类的私有成员
friend int MostExpensiveCar( CCar cars[], int total); //声明友元
A是B的友元类 A的成员函数可以访问B的私有成员
friend class CDriver; //声明CDriver为友元类
this指针
指向成员函数所作用的对象
C++翻译到C程序
静态成员函数中不能使用 this 指针!
因为静态成员函数并不具体作用与某个对象!
因此,静态成员函数的真实的参数的个数,就是程序中写出的参数个数
因为静态成员函数并不具体作用与某个对象!
因此,静态成员函数的真实的参数的个数,就是程序中写出的参数个数
常量对象
如果不希望某个对象的值被改变,则定义该对象的
时候可以在前面加const关键字。
时候可以在前面加const关键字。
常量成员函数
常量成员函数执行期间不应修改其所作用的对象
。因此,在常量成员函数中不能修改成员变量的值
(静态成员变量除外),也不能调用同类的非常量
成员函数(静态成员函数除外)。
。因此,在常量成员函数中不能修改成员变量的值
(静态成员变量除外),也不能调用同类的非常量
成员函数(静态成员函数除外)。
常量内不能赋值、不能调用非常量
常量成员函数的重载
两个成员函数,名字和参数表都一样,但是一个
是const,一个不是,算重载
是const,一个不是,算重载
常引用
子主题

收藏
0 条评论
下一页