C++面向对象程序设计-北京大学-郭炜【课程笔记(五)】
- 1、常量对象、常量成员函数
- 1.1、常量对象
- 1.2、常量成员函数
- 1.3、常引用
- 2、友元(friends)
- 2.1、友元函数
- 2.2、友元类
- 3、运算符重载的基本概念
- 3.1、运算符重载
- 4、赋值运算符的重载
- 4、重载赋值运算符的意义 – 浅拷贝和深拷贝
- 5、运算符重载为友元
写毕业论文中:学习速度较慢
开始课程:P14 4-4.常量对象、常量成员函数
课程链接:程序设计与算法(三)C++面向对象程序设计 北京大学 郭炜
课程PPT:github提供的对应课程PPT
1、常量对象、常量成员函数
1.1、常量对象
如果不希望某个对象的值被改变,则定义该对象的时候可以在前面加
const
关键字。
class Demo{
private :
int value;
public:
void SetValue() { }
};
const Demo Obj; // 常量对象
1.2、常量成员函数
- 在类的成员函数说明后面可以加
const
关键字,则该成员函数成为常量成员函数
。 - 常量成员函数执行期间
不应修改其所作用的对象
。因此,在常量成员函数中不能修改成员变量的值(静态成员变量除外
),也不能调用同类的非常量成员函数(静态成员函数除外
)。 -
- 解释:【因为静态成员对象不属于对象的一部分,被所有对象所共享。静态成员函数不会访问非静态的成员变量。】
实例:
class Sample
{
public:
int value; // 成员变量
void GetValue() const; // 常量成员函数
void func() { }; // 成员对象
Sample() { }; // 构造函数
};
void Sample::GetValue() const // 常量成员函数
{
value = o; // Error,常量成员函数中不能修改成员变量的值
func(); // Error,常量成员函数中不能调用同类的非常量成员函数,因为非常量成员函数可能修改成员变量的值
}
实例2:
#include<iostream>
class Sample
{
public:
int value;
void GetValue() const;
void func() {};
Sample() {}
};
int main()
{
const Sample o;
//o.value = 100; // err.常量对象不可被修改
//o.func(); // err.常量对象上面不能执行非常量成员函数
o.GetValue(); // OK,常量对象上可以执行常量成员函数
return 0;
}
// 在Dev C++中,要为Sample 类编写无参构造函数才可以,Visual Studio 2010中不需要
两个成员函数,名字和参数表都一样,但是一个是const,一个不是,算重载。
class CTest
{
private :
int n;
public:
CTest() { n = 1 ; }
int GetValue() const { return n ; }
int GetValue() { return 2 * n ; } // 函数重载关系
};
int main() {
const CTest objTest1; // 常量对象
CTest objTest2; // 非常量对象
std::cout << objTest1.GetValue() << std::endl; // 调用int GetValue() const { return n ; }
std::cout << objTest2.GetValue() << std::endl; // 调用int GetValue() { return 2 * n ; }
return 0;
}
// OUT
1
2
1.3、常引用
引用前面可以加
const
关键字,成为常引用。不能通过常引用,修改其引用的变量。如下实例:
const int & r = n;
r = 5; //error
n = 4; //ok
对象作为函数的参数时,生成该参数需要调用复制构造函数,效率比较低。
用指针作参数,代码又不好看,如何解决?
答:可以用对象的引用作为参数
可以用对象的引用作为参数,如:
class Sample {
…
};
void PrintfObj(Sample & o)
{
……
}
问题:对象引用作为函数的参数有一定风险性,若函数中不小心修改了形参o,则实参也跟着变,这可能不是我们想要的。如何避免?
答:使用对象的常引用
作为参数,如下实例。
class Sample {
…
};
void PrintfObj( const Sample & o)
{
……
}
// 这样函数中就能确保不会出现无意中更改o值的语句了。
2、友元(friends)
友元分为友元函数
和友元类
两种:
2.1、友元函数
1)友元函数:一个类的友元函数可以访问该类的私有成员。
实例:
#include<iostream>
class CCar; // 提前声明CCar类,以便后面的CDriver类使用
class CDriver
{
public:
void ModifyCar(CCar * pCar); // 改装汽车
};
class CCar
{
private:
int price;
friend int MostExpensiveCar( CCar cars[], int total); //声明友元
friend void CDriver::ModifyCar(CCar * pCar); //声明友元
};
// 通过友元函数直接访问CCar的私有成员变量
void CDriver::ModifyCar( CCar * pCar)
{
pCar->price += 1000; //汽车改装后价值增加
}
// 全局的普通函数
int MostExpensiveCar( CCar cars[], int total) //求最贵汽车的价格
{
int tmpMax = -1;
for( int i = 0; i < total; ++i )
if( cars[i].price > tmpMax)
tmpMax = cars[i].price; //cars[i]:是一个CCar对象
return tmpMax;
}
int main()
{
return 0;
}
将一个类的成员函数(包括构造, 析构函数) 说明为另一个类的友元。如下实例:
class B {
public:
void function();
};
class A {
friend void B::function();
};
2.2、友元类
友元类:如果A是B的友元类,那么A的成员函数可以访问B的私有成员。
class CCar {
private:
int price;
friend class CDriver; //声明CDriver为友元类
};
class CDriver {
public:
CCar myCar;
void ModifyCar()
{ //改装汽车
myCar.price += 1000; // CDriver是CCar的友元类可以访问其私有成员
}
};
int main()
{
return 0;
}
注意事项:Note
- 友元类之间的关系
- 不能传递, 不能继承
-
- 具体来说就是A是B的友元,B是C的友元,即A是C的友元(这是不可以的)
3、运算符重载的基本概念
C++预定义表示对数据的运算
- +, -, *, /, %, ^, &, ~, !, |, =, <<, >>, != ……
-
- 只能用于基本的数据类型,整型, 实型, 字符型, 逻辑型……
-
- 不能用于对象之间的运算
在数学上,两个附属可以直接进行+、-等运算。但在C++中,直接将+或-用于复数对象是不允许的。有时会希望,让对象也能通过运算符进行运算。这样代码更简洁,容易理解。
例如:complex_a和complex_b是两个复数对象;
- 求两个复数的和,希望能直接写成:complex_a + complex_b
3.1、运算符重载
实例:
注意事项:
- 重载为成员函数时:参数个数为运算符目数减一
- 重载为普通函数时:参数个数为运算符目数
#include<iostream>
class Complex
{
public:
double real, imag;
Complex(double r = 0.0, double i = 0.0):
real(r), imag(i)
{
}
Complex operator-(const Complex & c); // 重载为成员函数
};
// 重载为普通函数
Complex operator+(const Complex & a, const Complex & b)
{
return Complex(a.real + b.real, a.imag + b.imag); // 返回一个临时对象
}
Complex Complex::operator-(const Complex & c)
{
return Complex(real - c.real, imag - c.imag); // 返回一个临时对象
}
int main()
{
Complex a(4, 4), b(1, 1), c;
c = a + b; // 等价于c=operator+(a+b)
std::cout << c.real << "," << c.imag << std::endl;
std::cout << (a-b).real << "," << (a-b).imag << std::endl;
// a-b 等价于a.operator-(b)
return 0;
}
// OUT:
5,5
3,3
- c = a + b; 等价于c = operator(a,b);
- a-b 等价于a.operator-(b)
4、赋值运算符的重载
注意:以下教学视频中代码存在问题,请注意对比
#include<iostream>
#include<cstring>
using namespace std;
class String
{
private:
char * str;
public:
String () : str(new char[1]) {str[0] = 0;}
const char * c_str() { return str; }
String & operator = (const char * s); // 对赋值运算符进行重载
// 输入值是const char * s,返回值时String 的引用
~String( ) {delete [] str;}
};
// 重载 ‘=’ obj = “hello”能够成立
String & String::operator = (const char * s)
{
delete [] str;
str = new char[strlen(s)+1];
strcpy(str, s);
return * this;
}
int main()
{
String s; // s.str是char类型的
s = "Good Luck," ; // ”=“是重载,因为等号两边类型是不匹配的,等价于 s.operator=("Good Luck,")
std::cout << s.c_str() << std::endl;
// String s2 = “hello!”; //这条语句要是不注释掉就会出错,因为这是初始化语句
s = "Shenzhou 8!"; ”=“是重载,因为等号两边类型是不匹配的,等价于 s.operator=("Shenzhou 8!")
std::cout << s.c_str() << std::endl;
return 0;
}
// OUT
Good Luck,
Shenzhou 8!
4、重载赋值运算符的意义 – 浅拷贝和深拷贝
问题:上述图片中这样做够了吗?还有需要改进的地方吗?
此处,好好听课,不好解释:
5、运算符重载为友元
通常, 将运算符重载为类的成员函数重载为友元函数的情况:
- 成员函数不能满足使用要求
- 普通函数, 又不能访问类的私有成员
实例1:运算符重载
class Complex{
double real, imag;
public:
Complex(double r, double i):real(r), imag(i){ };
Complex operator+(double r); // 运算符重载
};
Complex Complex::operator+(double r)
{ //能解释 c+5
return Complex(real + r, imag);
}