面向对象特征:
抽象,多态,继承,封装
1.条件编译
#if 0 #if 1 #ifdef #ifndef
... ... ... ...
#endif #else #endif #else
... ...
#endif #endif
***********/
2.输入输出流,名字空间
using namespace std;
using std::cin;
std::cin>>i;
3.访问和内部作用域变量同名的全局变量,要用全局作用域限定 ::
#include <iostream>
using namespace std;
double a = 128;
int main (){
double a = 256;
cout << "Global a: " <<::a << endl; //::是全局作用域限定
return 0;
}
4. 通过 try-catch处理异常情况 :正常代码放在try块,catch中捕获try块抛出的异常
try { //放置正常代码,但可能出现问题
if (a > 100) throw 100; //throw 抛出异常(也称为抛弃异常),100为异常对象,出现异常则下面的语句不会执行
if (a < 10) throw 10; //throw后下面语句便不执行了
throw "hello";
}
catch (int result) { //捕获异常
........
}
catch (char * s) { //捕获字符串
........
}
5. 引用(别名),引用主要用于函数形参,C语言函数形参都是值参数
int a = 3, &r = a; //r就是a
void swap(int &x, int &y) { //利用别名,形参x就是实参a
int t = x;
x = y;
y = t;
}
6.默认形参
void print(char ch, int n = 1) { //n为默认形参,默认形参必须在右边。n若不传递参数就为1,传递则是传递的数
for (int i = 0; i < n; i++)
cout << ch;
}
7 函数重载 :允许有同名函数存在,只要形参不同,注意:不能根据返回类型区分同名函数
函数重载,函数重写,同名隐藏
https://blog.csdn.net/inter_peng/article/details/53940179
https://blog.csdn.net/u014725884/article/details/47213651
8函数模板,类模板
template<typename T> //函数模板, typename 也可写成class
T add(T x, T y) {
return x + y;
}
int main() {
// cout << add<int>(5, 3) << endl;
cout << add(5.3, 7.8) << endl;
cout << add((double)5, 7.8) << endl; //歧义性 ,//函数模板是不允许隐式类型转换的,调用时类型必须严格匹配
}
//类模板
template<class T>
class Array {
T size;
T *data;
public:
Array(int s) { //构造函数
size = s;
data = new T[s];
}
virtual ~Array() {
delete[] data;
}
};
9.string,vector,迭代器iterator
string类具体操作: https://www.cnblogs.com/X-Do-Better/p/8628492.html
//vector
#include <iostream>
#include <vector> // vector是一个类模板
using namespace std;
int main() {
vector<int> v = { 7, 5, 16, 8 };
vector<int > V2;
v.push_back(25); //push_back(),最后添加一个元素 //成员函数size()、下标运算符[]
int i = v.size();
v.pop_back(); //删除最后元素
v.resize(2);
}
string::const_iterator cii; //const迭代器,不能修改, iterator能修改 string::iterator cii;
10.new/delete
int *p = new int; delete p; //防止内存泄漏
int *p = new int[n]; delete[] p;
11. this指针: 指向类对象自己,成员函数实际上隐含一个this指针
#include <iostream>
#include <string>
using namespace std;
struct student {
string name; double score;
void print() {
cout << this->name << " " << this->score << endl;
}
};
int main() {
student stu;
stu.name = "Li Ping"; stu.score = 78.5;
stu.print(); // print(&stu);
}
12.访问控制、类接口
struct和class区别: struct里的成员默认是public(公开的) class里的成员默认是private(私有的)
接口:public的公开成员(一般是成员函数)称为这个类的对外接口,外部函数只能通过这些接口访问类对象,
private等非public的包含内部内部细节,不对外公开,从而可以封装保护类对象!
13.构造函数 ,拷贝构造函数,析构函数
构造函数是和类名同名且没有返回类型的函数,在定义对象时会自动被调用,而不需要在单独调用专门的初始化函数如init
构造函数用于初始化类对象成员,包括申请一些资源,如分配内存、打开某文件等
如果不写构造函数,编译器会默认生成一个String(){ }
析构函数是在类对象销毁时被自动调用,用于释放该
为了防止内存泄漏的发生,最好将基类的析构函数写成virtual虚析构函数。
默认的“拷贝构造函数”是“硬拷贝”或“逐成员拷贝”,指针指向同一块内存区域,当多次释放同一块内存就出错了!
#include <iostream>
using namespace std;
class String {
char *data; //C风格的字符串
int n; //字符个数
public:
~String() {
..........
if(data)
delete[] data;
}
String(const String &s) { // 重定义拷贝构造函数 硬拷贝{data=s.data; s=s.n},
//执行默认拷贝构造函数时s3和str2指向同一个地址,改变s3那么str2也改变
cout << "拷贝构造函数!\n";
//当需要申请资源时,后需要将资源释放掉,最好重新定义拷贝构造函数,防止析构时多次释放
data = new char[s.n + 1];
........
}
String(const char *s=0) { //构造函数 ,:函数名和类名相同且无返回类型的成员函数
. . . . .
}
............
};
ostream& operator<<(ostream &o, String s) {
for (int i = 0; i < s.size(); i++)
cout << s[i];
return o;
}
void f() {
String str,str2("hello world");
str2[1] = 'E';
// cout << str2 << endl;
String s3 = str2; //拷贝构造函数 执行默认拷贝构造函数时s3和str2指向同一个地址,改变s3那么str2也改变
cout << str2 << endl; //str2传递给s调用ostream& operator<<(ostream &o, String s) 时 会默认调用拷贝构造函数
//调用时s和str2指向同一个地址,结束后调用析构函数s销毁,
//当f函数结束时,str2会销毁,造成了同一个内存多次销毁 所以要自定义拷贝构造函数
}
14.运算符重载:针对用户定义类型重新定义运算符函数
运算符与普通函数在调用时的不同之处是:对于普通函数,参数出现在圆括号内;而对于运算符,参数出现在其左、右侧。
Complex a, b, c;
…
c = Add(a, b); // 用普通函数
c = a + b; // 用运算符+
class Point{
double x, y;
public:
double operator[](int i) const{ //const函数 。无法给通过下标赋值,为了和下面不一样。加了const,返回x复制品
if (i == 0) return x;
else if (i == 1) return y;
else throw "下标非法!"; //抛出异常
}
double& operator[](int i) { //下标可修改,返回x本身
if (i == 0) return x;
else if (i == 1) return y;
else throw "下标非法!"; //抛出异常
}
Point(double x_,double y_) //构造函数
x = x_; y = y_;
}
Point operator+(const Point q) { //作为类的内部函数第一个参数就是调用操作本身。
return Point(this->x+q[0],this->y + q[1]);
}
friend ostream & operator<<(ostream &o, Point p); //友元函数
friend istream & operator>>(istream &i, Point &p);
};
ostream & operator<<(ostream &o, Point p) {
o <<p.x << " " << p.y<< endl;
return o;
}
istream & operator>>(istream &i, Point &p) {
i >> p.x >> p.y;
return i;
}
/* Point operator+(const Point p,const Point q) { return Point(p[0] + q[0], p[1] + q[1]); } */
int main() {
Point p(3.5, 4.8),q(2.0,3.0); //自动调用构造函数
cout << p;
cout << p[0] << "-" << p[1] << endl; //p.operator[](0)
p[0] = 3.45; p[1] = 5.67;
cout << p<<q;
Point s = p + q; //p.operator+(q) 内部调用vs operator+(p,q)外部调用
cout << s;
}
友元函数:为了使其他类的成员函数直接访问该类的私有变量。即:允许外面的类或函数去访问类的私有变量和保护变量,从而使两个类共享同一函数。
实际上具体大概有下面两种情况需要使用友元函数:(1)运算符重载的某些场合需要使用友元。(2)两个类要共享数据的时候
15. Inheritance继承(Derivation派生): 一个派生类(derived class)
多重继承: 从一个类派生出多个不同的类
多重派生: 从多个不同的类派生出一个类来
. 从1个或多个父类(parent class) / 基类(base class)继承,即继承父类的属性和行为,但也有自己的特有属性和行为.
#include <iostream>
#include <string>
using namespace std;
class Employee{
string name;
public:
Employee(string n);
void print();
};
class Manager: public Employee{ //子类
int level;
public:
Manager(string n, int l = 1);
//void print();
};
Employee::Employee(string n) :name(n)//初始化成员列表
{
//name = n;
}
void Employee::print() {
cout << name << endl;
}
Manager::Manager(string n, int l) :Employee(n), level(l) {
}
//通过调用基类的构造函数对基类成员初始化
//派生类的构造函数只能描述它自己的成员和其直接基类的初始式,不能去初始化基类的成员。
Manager::Manager(string n, int l) : name(n), level(l) { //错的
}
int main() {
Manager m("Zhang",2);
Employee e("Li");
m.print();
e.print();
}
//子类和父类有同名函数
class Manager : public Employee
{
int level;
public:
Manager(string n, int l = 1);
void print();
};
Manager::Manager(string n, int l) :Employee(n), level(l) {
}
void Manager::print() {
cout << level << "\t";
Employee::print();
}
int main() {
Manager m("Zhang");
Employee e("Li");
m.print(); //打印的是manage的print
e.print();
}
派生类的指针可以自动转化为基类指针
int main() {
Employee *p;
Manager m("Zhang", 1);
p = &m;
p->print(); //此时打印的是employee中成员不是manage的
}
16.虚函数Virtual Functions,多态
上面代码可以将print声明为虚函数Virtual Functions :用virtual关键字,根据实际指向的类型输出,多态性,否则指针输出的是基类
静态成员函数不能是虚函数。
1.基类中必须包含虚函数(在成员函数之前加上virutal 关键字),并且在派生类中对基类中的虚函数进行重写
2.在派生类中重写的函数在基类中必须是虚函数(派生类中可以加virtual 关键字,不加的时候仍然会保持虚函数特性,但是建议加上)
3.派生类中虚函数必须与基类中虚函数的原型保持一致(返回值,函数名,参数列表和返回值都要相同,如果不同则会构成同名隐藏)
例外:析构函数(函数名不同)
例外:协变:基类的虚函数返回基类的引用或指针,派生类的虚函数返回派生类的引用或指针
4.基类与派生类中函数的访问权限可以不同,不过基类中虚函数必须是public权限
5.通过基类的指针或者引用来调用虚函数
6.静态成员函数不能定义为虚函数
多态:对于通过基类指针调用基类和派生类中都有的同名、同参数表的虚函数的语句,编译时并不确定要执行的是基类还是派生类的虚函数;而当程序运行到该语句时,如果基类指针指向的是一个基类对象,则基类的虚函数被调用,如果基类指针指向的是一个派生类对象,则派生类的虚函数被调用。这种机制就叫作“多态。
17.纯虚函数和抽象类
主要作用:相当于上述的基类,抽象类专门用来存放多态的接口,它是所有要实现多态的类的基类。
一 般的抽象类只是为了构成多态,并且其中的纯虚函数只是为了在其它的类种进行重写。
如果一个类继承于一个虚函数,那么这个函数种必须要重写抽象类中的纯虚函数。
写法:只有public的成员函数,并且只是声明,而且声后面加上 = 0,此函数叫做纯虚函数。
构成:以纯虚函数作为成员函数的类叫做抽象类,抽象类不能直接实例化,但是抽象类的指针是可以定义的。
18.内联函数:inline
内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
void Foo(int x, int y);
inline void Foo(int x, int y) // inline与函数定义体放在一起
{
…
}
对于任何内联函数,编译器在符号表里放入函数的声明(包括名字、参数类型、返回值类型)。如果编译器没有发现内联函数存在错误,那么该函数的代码也被放入符号表里。在调用一个内联函数时,编译器首先检查调用是否正确(**进行类型安全检查,或者进行自动类型转换,**当然对所有的函数都一样)。如果正确,内联函数的代码就会直接替换函数调用,于是省去了函数调用的开销。这个过程与预处理有显著的不同,因为预处理器不能进行类型安全检查,或者进行自动类型转换。假如内联函数是成员函数,对象的地址(this)会被放在合适的地方,这也是预处理器办不到的。
C++ 语言的函数内联机制既具备宏代码的效率,又增加了安全性,而且可以自由操作类的数据成员。所以在C++ 程序中,应该用内联函数取代所有宏代码,“断言assert”恐怕是唯一的例外。assert 是仅在Debug版本起作用的宏,它用于检查“不应该”发生的情况。为了不在程序的Debug版本和Release版本引起差别,assert 不应该产生任何副作用。如果assert是函数,由于函数调用会引起内存、代码的变动,那么将导致Debug版本与Release版本存在差异。所以assert 不是函数,而是宏。