OOP 学习笔记(2)——封装与接口
Contents
封装与接口
函数重载
同一名称的函数,有两个以上不同的函数实现,被称为函数重载。
比如:
int ADD(int a,int b)
{
return a+b;
}
int ADD(int a,int b,int c)
{
return a+b+c;
}
编译器将根据函数调用语句的实际参数决定调用哪个函数。
多个同名函数之间必须保证可以通过参数列表区分,返回值、参数名称不能作为区分标识。
函数参数的缺省值
也就是讲过的函数参数初始值。
同样保证需要可区分。
基础知识
auto
由编译器根据上下文自动确定变量的类型,如:
auto i = 3; //i是int型变量
auto f = 4.0f; //f是float型变量
auto a('c'); //a是char型变量
auto b = a; //b是char型变量
auto *x = new auto(3); //x是int*
追踪返回类型的函数
可以将函数返回类型的声明信息放到函数参数列表的后面进行声明。如:
- 普通函数声明形式:
int func(char *ptr, int val);
- 追踪返回类型的函数声明形式:
auto func(char *ptr, int val) -> int;
追踪返回类型在原本函数返回值的位置使用 auto
关键字。
decltype
decltype
可以对变量或表达式结果的类型进行推导。- 重用匿名类型。
- 泛型编程中结合
auto
,用于追踪函数的返回值类型。
struct
{
//...
}a, c[10];
int main()
{
decltype(a) b;
decltype(c) d;
//定义了一个上面匿名的结构体
}
结合 auto
和 decltype
- 可以推导返回类型:
auto func(int x, int y) -> decltype(x + y) { return x + y; }
- C++14 中不再需要显式指定返回类型:
auto func(int x, int y) { return x + y; }
内存的动态申请和释放
也就是 new
/delete
运算符。
直接举例就是:
int *ptr = new int;
int *array = new int[10];
delete ptr;
delete [] array;
跟前面的 malloc()
和 free
很像,不做更多说明。
NULL
、$0$、nullptr
在 C++ 中,NULL
被定义为 $0$。
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
在 C++11 之前,可以使用 NULL
或者 $0$ 表示空指针。
然而这样会产生一些问题:
void f(int x, double *y)
{
//...
}
f(2, NULL); // 程序可能报错
f(2, static_cast<double *>(0)); // 程序正常运行
我们可能会忽略 NULL
同时是一个 int
型常量的事实。
而 nullptr
表示严格意义上的空指针。
此时上面程序就不会报错。
基于范围的 for
循环
形式为:
for (type key : range)
//...
其中 key
是用于迭代的变量,range
表示被迭代的范围。
如:
int arr[] = {2, 3, 5, 7, 11};
for (int e : arr)
cout << e << endl;
用户定义类型——类 class
class
——用户自定义的类型
- 包含函数、数据的加强版“结构体”(C 语言中的)。
- 其中包含函数和数据称为成员函数和成员变量。
成员函数必须在类内声明,但定义可以在类内或者类外。
- 利用这点,往往会在头文件中声明类,并在实现文件中定义成员函数。
- 一般将不同的类保存为不同的头文件和实现文件。
- 当然实际上也可以在类内直接定义成员函数,但复杂成员函数一般还是声明、定义分离的 。
class Matrix { public: void fill(char dir); private: int data[6][6]; }; void Matrix::fill(char dir) { data[0][0] = 1; //... }
类成员的访问权限
类的成员变量和函数可以根据需要设置不同的访问权限:
public
:可以在类外用.
操作符访问。private
:- 默认权限;(没有注明访问权限情况下)
- 不允许在类外用
.
操作符访问。
protected
:暂时不介绍。
具体实例可以自行尝试。
this
指针
所有成员函数的参数中,隐含一个指向当前对象的指针变量,名称为 this
。
如上面的 fill()
函数中,就可以将 data
改为 this->data
而不改变效果。
类的运算符重载
用户自定义类,没有对重用运算符进行定义,比如对于两个类对象,难以使用 a+b
。
所以,C++ 支持了对以下运算符的重载:
+
、-
、*
、/
、[]
、()
、<<
、>>
、++
、--
对于加减乘除我们就以 +
代替,其定义一般为:
return_type operator+(parameters)
{
//...
}
比如上面的 Matrix
类可以定义矩阵加法:
Matrix operator+(const Matrix &A, const Matrix &B)
{
Matrix ret;
for (int i = 0; i < 6; i++)
for (int j = 0; j < 6; j++)
ret.data[i][j] = A.data[i][j] + B.data[i][j];
return ret;
}
对象输入输出——流运算符重载
用户自定义的类,虽然可以像内置类型那样定义变量(对象),但想要使用流运算符输入、输出对象,则还需要为其定义流运算符重载。
比如想要实现这样的功能:
Matrix A;
cin >> A;
//...
cout << A;
流运算符重载函数的声明:
istream& operator>>(istream& in, Type& dst);
ostream& operator<<(ostream& out, const Type& src);
一种可能的实现为:
#include <iostream>
#include <fstream>
using namespace std;
class Matrix
{
int data[6][6];
public:
friend istream& operator>>(istream& in, Matrix& dst);
friend ostream& operator<<(ostream& out, const Matrix& src);
};
istream& operator>>(istream& in, Matrix& dst)
{
for (int i = 0; i < 6; i++)
for (int j = 0; j < 6; j++)
in >> dst.data[i][j];
return in;
}
ostream& operator<<(ostream& out, const Matrix& src)
{
for (int i = 0; i < 6; i++)
for (int j = 0; j < 6; j++)
out << dst.data[i][j] << (j == 5 ? '\n' : ' ');
return out;
}
函数运算符 ()
重载
这种重载让对象看上去像是一个函数名:
return_type operator()(parameters)
{
//...
}
使用方法:
ClassName Obj;
Obj(real_parameters);
// -> Obj.operator()(real_parameters);
此时 Obj
对象看上去就像是一个函数,故也称为函数对象,似乎也被称为伪函数。
数组下标运算符 []
重载
与前面的基本相同,但是注意只能传入一个参数。
当然这个参数的类型倒是基本没有限制,也就是可以以此构造广义的数组。
注意,如果返回值是引用,则可以作为左值,否则不行。
一个实例:
#include <iostream>
#include <string>
using namespace std;
char week_name[7][4] = {"mon", "tu", "wed", "thu", "fri", "sat", "sun"};
class WeekTemp
{
int temp[7];
public:
int& operator[] (const char* name) // 字符串作下标
{
for (int i = 0; i < 7; i++)
{
if (strcmp(week_name[i], name) == 0)
return temp[i];
}
}
};
int main()
{
WeekTemp beijing;
beijing["mon"] = -3;
beijing["tu"] = -1;
cout << "Monday Temperature: "
<< beijing["mon"] << endl;
return 0;
}
输出结果:
Monday Temperature: -3
前缀与后缀的 ++
、--
前缀运算符重载声明:
ClassName operator++();
ClassName operator--();
后缀运算符重载声明:
ClassName operator++(int dummy);
ClassName operator--(int dummy);
通过在函数体中没有使用的哑元参数 dummy
来区分前缀与后缀的同名重载。
一般哑元只定义但没有名称,比如:
#include <iostream>
using namespace std;
class Test
{
public:
Test operator++()
{
cout << "operator++()" << endl;
}
Test operator++(int)
{
cout << "operator++(int)" << endl;
}
};
int main()
{
Test test;
test++;
++test;
return 0;
}
友元
在类内进行友元的声明。
被声明为友元的函数或类,具有对出现友元声明的类的 private
和 protected
成员的访问权限,也就是可以访问该类的一切成员。
友元修饰的函数或类,不受 private
的影响。
class Test
{
int data;
friend void f(Test &test);
};
void f(Test &test)
{
cout << test.data << endl;
}
上面的流运算符重载就是需要访问私有成员 data
数组才会加上友元声明。
也可以声明别的类的成员函数,为当前类的友元(包括构造函数、析构函数,后面内容):
class A
{
void f(int);
A(int x)
{
//...
}
~A()
{
//...
}
};
class B
{
int x;
friend void A::f(int);
friend A::A(int x),A::~A();
};
同时也可对 class
、struct
、union
进行友元声明。
对其他类型的友元声明只会被忽略。
class A {};
class B
{
friend class C; // 友元类前置声明(详细类型指定符)
friend A; // 友元类声明(简单类型指定符)(c++11 起)
};
class C {};
而一个函数也可以是多个类的友元函数。
其他注意事项还有:
- 友元不传递。
- 友元不继承。
- 友元声明不能用于定义新的
class
。
内联函数
用途
使用内联函数,编译器自动产生等价的表达式。
inline int max(int a, int b)
{
return a > b ? a : b;
}
cout << max(a, b) << endl;
等价于:
cout << (a > b ? a : b) << endl;
和宏定义的区别
首先,宏定义是直接拷贝,可能与函数调用有不同的结果,容易出错,很多缺陷不可避免。
其次,内联函数在 Debug 版本中,没有真正内联,仍然可以调试,在 Release 版本,才实现了真正的内联。
最后,宏定义的函数无法操作私有数据成员。
注意事项
- 避免对大段代码使用内联修饰符。
- 避免对包含复杂结构的函数使用内联定义。
- 内联修饰用于函数定义而不是函数声明。
- 定义在类声明中的函数默认为内联函数。
- 一般构造函数、析构函数都被定义为内联函数。
- 在头文件加入或修改
inline
函数时,调用此头文件的源文件均需要重新编译。 - 内联修饰符更像是建议而非命令,实际上编译器往往会有判断性、智能地选择是否内联。
No Comments