C++框架
A better C
overloading
同名函数,不同参数,不要更换返回值类型
void fun(int i){
}
void fun(char *p){ //字符串
}
default parameter
void fun(int a, int b = 3, int c = 4)//默认参数应该放在参数声明的最后
减少终端程序员写参数的个数,但编译器会默认传递默认参数,不会提高程序性能。
fun(2); //Yes
fun(2, , 5); //NO
fun(2, 3, 5); //Yes
fun(2, 3); //Yes
from struct to class
在C语言中,struct 只能包含成员变量,不能包含成员函数。而在C++中,struct 类似于 class,既可以包含成员变量,又可以包含成员函数。
C++中的 struct 和 class 基本是通用的,唯有几个细节不同:
- 使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的;
- class 继承默认是 private 继承,而 struct 继承默认是 public 继承;
- class 可以使用模板,而 struct 不能。
建议使用 class 来定义类,而使用 struct 来定义结构体。
封装
构造
默认构造
public:
Person(); //默认构造
Person(int aage,char *aname); //普通构造
int main() {
Test t(); //函数声明
Test t; //默认构造的对象
}
拷贝构造
-
浅拷贝(默认构造函数)
class Test{ private: int i; int *j; public: Test(int ai, int aj=0); ~Test(); }; int main() { Test t1(1,2); Test t2(t1);//浅拷贝(bitwise) ,同java中的clone return 0; }
-
深拷贝
class Test{ private: int i; int *j; public: Test(int ai, int aj=0); Test(Test& t);//增添拷贝函数 ~Test(); }; Test::Test(Test& t)//拷贝函数 { this->i = t.i; this->j = (int *)malloc(sizeof(int)); *j = *t.j; } int main() { Test t1(1,2); Test t2(&t1);//深拷贝(logical copy) return 0; }
析构
销毁对象时系统会自动调用一个函数来进行清理工作,例如释放分配的内存、关闭打开的文件等,这个函数就是析构函数。
析构函数(Destructor)也是一种特殊的成员函数,没有返回值,不需要程序员显式调用(程序员也没法显式调用),而是在销毁对象时自动执行。构造函数的名字和类名相同,而析构函数的名字是在类名前面加一个~
符号。
一个类只能有一个析构函数。如果用户没有定义,编译器会自动生成一个默认的析构函数。
class Test{
private:
int i;
int *j;
public:
Test(int ai, int aj=0);
~Test();//destructor 析构函数
};
Test::~Test()//如果没有显式定义,自动生成
{
free(j);
}
访问控制
private | 外界私有 |
---|---|
public | 外界公有 |
protected | 子类公有,外界私有 |
引用
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
引用与指针之间有三个主要的不同:
- **不存在空引用。**引用必须连接到一块合法的内存。
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
- 引用必须在创建时被初始化。指针可以在任何时间被初始化。
示例如下:
//指针
void fun(int *a)
{
(*a)++;
}
int main()
{
int m = 10;
fun(&m); //传入地址
cout << m << endl;
return 0;
}
//引用
void fun(int& a)
{
a++;
}
int main()
{
int m = 10;
fun(m); //传入自身
cout << m << endl;
return 0;
}
在C++中,&表示取地址操作符,也表示引用,具体看上下文环境。当&用作声明时,表示的是引用,其它时候表示取地址,如果声明的是指针类型,和C一样是*。
int a =1;
int &b = a; //&表示引用
int &c; //Compile Error
int *d = &a; //c是int指针类型,&表示取地址
this
void Student::settime(char *name) {
this->name = name; //给name成员赋值
name = name; //给形参赋值
}
static
-
static local variable 创建变量永不消失直至程序结束
void fun(){ static int i = 0;//保值、计数 cout << i << endl; }
-
static global function 跨文件
extern int g_i; //外链接 void fun(); //可见域:函数:默认全局可见; 全局变量:默认本文件可见 int main(){ count << g_i << endl; return 0; }
全局变量绝对不能定义在头文件中
static void fun(){} //将函数的可见域限制在本文件内,其他文件无法链接
-
static data member 类内通信
class Test { public: static int i;//负责类内通信,全类的共享空间 int j; public: Test(int ai, int aj); }; Test::Test(int ai, int aj) { //i = ai;//不应该再对类内通信的信使随意赋值 j = aj; } int Test::i = 100; int main() { Test t1(2); Test t2(3); cout << t1.i << endl;//100 t1.i = 200; cout << t2.i << endl;//200 return 0; }
-
static function member
n1类的静态函数可以通过类名::函数名直接调用,非静态函数必须有对象才能调用成员函数
class Test { public: static int i;//负责类内通信,全类的共享空间 int j; public: Test(int ai, int aj); static void fun(); }; void Test::fun() { this->i = 10;//OK~!!! this->j = 10;//ERROR! 一旦有static修饰的函数,不可以修饰非static的变量 } int main() { Test t1(2); Test::fun(); }
运算符重载
运算符重载(Operator Overloading)是指同一个运算符可以有不同的功能。
#include <iostream>
using namespace std;
class complex{
public:
complex();
complex(double real, double imag);
public:
//声明运算符重载
complex operator+(const complex &A) const;
void display() const;
private:
double m_real; //实部
double m_imag; //虚部
};
complex::complex(): m_real(0.0), m_imag(0.0){ }
complex::complex(double real, double imag): m_real(real), m_imag(imag){ }
//实现运算符重载
complex complex::operator+(const complex &A) const{
complex B;
B.m_real = this->m_real + A.m_real;
B.m_imag = this->m_imag + A.m_imag;
return B;
}
void complex::display() const{
cout<<m_real<<" + "<<m_imag<<"i"<<endl;
}
int main(){
complex c1(4.3, 5.8);
complex c2(2.4, 3.7);
complex c3;
c3 = c1 + c2;
c3.display();
return 0;
}
const
函数:
- 函数开头的 const 用来修饰函数的返回值,表示返回值是 const 类型,也就是不能被修改,例如
const char * getname()
。
const char * Student::getname()
{
return this->name;
}
- 函数头部的结尾加上 const 表示常成员函数,这种函数只能读取成员变量的值,而不能修改成员变量的值,例如
char * getname() const
。
char * Student::getname() const{
return m_name;
}
对象:
在C++中,const 也可以用来修饰对象,称为常对象。一旦将对象定义为常对象之后,就只能调用类的 const 成员(包括 const 成员变量和 const 成员函数)了。
new/delete
int main()
{
Test *p = new Test(1,2);//new = malloc(void*) + constructor(this)
Test *p = new Test[10];//构造10个对象,若想编译通过,必须有默认构造,否则不通过
delete []p;//释放对象数组,delete = destructor(释放引用里的malloc,heap中的空间) + free(释放引用的空间)
}
继承
概念(继承与组合)
继承
父类<——>子类
基类<——>派生类
声明派生类语法:
class Student : public(默认为private) Father
{
new members;
}
示例:
class People
{
private:
int id;
char *name;
public:
void set_name(char *aname);
void print_name();
};
class Student:public People
{
private:
int student_id;
public:
void set_student_id(int id);
int get_student_id();
};
组合
某个类包含另一个类的对象作为其成员变量之一时,就会出现类组合。
class StreetAddress
{
private:
string line1, line2;
public:
void setLine1(string);
void setLine2(string);
string getLine1();
string getLine2();
};
class PersonData
{
private:
string name;
StreetAddress address;
public:
...
};
重用(重新定义父类方法)
实现重用的两种方法:继承&组合。
构造初始化顺序问题
-
不显示调用基类构造函数
#include "iostream" using namespace std; class B1 { public: B1(){cout<<"B1"<<endl;} }; class B2 { public: B2(){cout<<"B2"<<endl;} }; class C:public B1,public B2 { public: C(){cout<<"C"<<endl;} }; int main() { C obj; return 0; } /*output: B1 B2 C */
-
显示调用
B1和B2的构造顺序只和继承的顺序有关,和成员初始化列表中的顺序无关
#include "iostream" using namespace std; class B1 { public: B1(){cout<<"B1"<<endl;} B1(int i){cout<<"B1:"<<i<<endl;} }; class B2 { public: B2(){cout<<"B2"<<endl;} B2(int i){cout<<"B2:"<<i<<endl;} }; class C:public B2,public B1 { public: C():B1(10),B2(20){cout<<"C"<<endl;}; }; int main() { C obj; return 0; } /*output: B2:20 B1:10 C */
私有、多重继承
class Base1
{
public:
void f();
};
class Base2
{
public:
void h();
void f();
};
class Derived : public Base1, public Base2
{
//这样可能会导致菱形结构,父类有同名函数导致冲突
//用composition可以解决这个问题,令Base1作为类组成成员
};
多态
基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式,我们将这种现象称为多态。
upcasting(向上类型转化)
将派生类对象赋值给基类对象、将派生类指针赋值给基类指针、将派生类引用赋值给基类引用,这在 C++ 中称为向上转型(Upcasting)
virtual虚函数
有了虚函数,基类指针指向基类对象时就使用基类的成员(包括成员函数和成员变量),指向派生类对象时就使用派生类的成员。使用虚函数非常简单,只需要在函数声明前面增加 virtual 关键字。
示例:
using namespace std;
//基类People
class People{
public:
People(char *name, int age);
virtual void display(); //声明为虚函数
protected:
char *m_name;
int m_age;
};
People::People(char *name, int age): m_name(name), m_age(age){}
void People::display(){
cout<<m_name<<"今年"<<m_age<<"岁了,是个无业游民。"<<endl;
}
//派生类Teacher
class Teacher: public People{
public:
Teacher(char *name, int age, int salary);
virtual void display(); //声明为虚函数
private:
int m_salary;
};
Teacher::Teacher(char *name, int age, int salary): People(name, age), m_salary(salary){}
void Teacher::display(){
cout<<m_name<<"今年"<<m_age<<"岁了,是一名教师,每月有"<<m_salary<<"元的收入。"<<endl;
}
int main(){
People *p = new People("王志刚", 23);
p -> display();
p = new Teacher("赵宏佳", 45, 8200);
p -> display();
return 0;
}
/*
output:
王志刚今年23岁了,是个无业游民。
赵宏佳今年45岁了,是一名教师,每月有8200元的收入。
*/
/*
反之,如果没有使用虚函数,得到的结果是:
王志刚今年23岁了,是个无业游民。
赵宏佳今年45岁了,是个无业游民。
*/
动态绑定/静态绑定
- 静态类型:对象在声明时采用的类型,在编译期既已确定;
- 动态类型:通常是指一个指针或引用目前所指对象的类型,是在运行期决定的;
- 静态绑定:绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期;
- 动态绑定:绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期;
-
静态绑定发生在编译期,动态绑定发生在运行期;
-
对象的动态类型可以更改,但是静态类型无法更改;
-
要想实现动态,必须使用动态绑定;
-
在继承体系中只有虚函数使用的是动态绑定,其他的全部是静态绑定;
多态的典型场景
构造析构的多态性
在C++的类继承中,建立对象时,首先调用基类的构造函数,然后在调用下一个派生类的构造函数,依次类推;而析构对象时,其顺序正好与构造相反。
在基类析构函数声明为virtual的时候,delete基类指针,会先调用派生类的析构函数,再调用基类的析构函数。
在基类析构函数没有声明为virtual的时候,delete基类指针,只会调用基类的析构函数,而不会调用派生类的析构函数,这样会造成销毁对象的不完全。
多态的机理(v-table,v-ptr)
当类中声明虚函数时,编译器会在类中生成一个虚函数表;
虚函数表是一个存储成员函数指针的数据结构,是由编译器自动生成与维护的,virtual成员函数会被编译器放入虚函数表中;
而存在虚函数时,每个对象都有一个指向虚函数的指针(vptr指针),在实现多态的过程中,父类和派生类都有vptr指针。
对象在创建时,由编译器对vptr指针进行初始化;
只有当对象的构造完全结束后vptr的指向才最终决定下来;
父类对象的vptr指向父类的虚函数表,子类对象的vptr指向子类的虚函数表。
定义子类对象时,vptr先指向父类的虚函数表,在父类构造完成之后,子类的vptr才指向自己的虚函数表。(这也就是在父类或者子类的构造函数中调用虚成员函数不会实现多态的原因,这是一道面试题)
abstract class & interface
C++的关键字中并没有interface,interface仅有接口声明,而且所有的声明默认的访问权限是public而非private(是不是想到了C++中的struct?)。
对于C++来说,这相当于抽象类的概念,即其中的成员函数都是纯虚函数,只有声明,没有实现。如:
class abstractClass{
virtual memfunc1() = 0;
virtual memfucn2() = 0;
};
这是一个用于实现接口的纯抽象类,仅包括纯虚函数的类(一般用作基类,派生类进行具体的实现)。纯虚函数是指用=0标记的虚函数。
多重继承
其他
设计模式
单件模式
单例模式也称为单件模式、单子模式,可能是使用最广泛的设计模式。其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
#include<iostream>
using namespace std;
class Singelton
{
private:
Singelton() {}
static Singelton * m_singel;
public:
static Singelton * getInstance()
{
if (NULL == m_singel)
{
std::cout << "创建实例" << std::endl;
m_singel = new Singelton();
}
return m_singel;
}
};
Singelton * Singelton::m_singel = NULL;
int main()
{
using namespace std;
// 单例模式
Singelton * p1 = Singelton::getInstance();
Singelton * p2 = Singelton::getInstance();
if (p1 == p2)
std::cout << "一致" << std::endl;
else
std::cout << "不一致" << std::endl;
return 0;
}
******************************工厂模式
//简单工厂模式
#include<iostream>
using namespace std;
//产品的基类
class Product{
public:
//基类中的纯虚函数
virtual int operation(int a, int b) = 0;
};
//产品的子类Add
class Product_Add : public Product{
public:
int operation(int a, int b){
return a + b;
}
};
//产品的子类Mul
class Product_Mul : public Product{
public:
int operation(int a, int b){
return a * b;
}
};
//工厂
class Factory{
public:
Product* Create(int i){
switch (i){
case 1:
return new Product_Add;
break;
case 2:
return new Product_Mul;
break;
default:
break;
}
}
};
int main()
{
Factory *factory = new Factory();
int add_result = factory->Create(1)->operation(1, 2);
int mul_result = factory->Create(2)->operation(1, 2);
cout <<"op_add:" <<add_result << endl;
cout <<"op_multiply:" << mul_result << endl;
getchar();
return 0;
}
*****************************观察者模式
当对象间存在一对多关系时,则使用观察者模式。比如,当一个对象被修改时,则会自动通知依赖它的对象。
实例:
#include <iostream>
#include <list>
using namespace std;
//观察者
class Observer
{
public:
Observer() {}
virtual ~Observer() {}
virtual void Update() {}
};
//博客
class Blog
{
public:
Blog() {}
virtual ~Blog() {}
void Attach(Observer *observer) { m_observers.push_back(observer); } //添加观察者
void Remove(Observer *observer) { m_observers.remove(observer); } //移除观察者
void Notify() //通知观察者
{
list<Observer*>::iterator iter = m_observers.begin();
for(; iter != m_observers.end(); iter++)
(*iter)->Update();
}
virtual void SetStatus(string s) { m_status = s; } //设置状态
virtual string GetStatus() { return m_status; } //获得状态
private:
list<Observer* > m_observers; //观察者链表
protected:
string m_status; //状态
};
//具体博客类
class BlogCSDN : public Blog
{
private:
string m_name; //博主名称
public:
BlogCSDN(string name): m_name(name) {}
~BlogCSDN() {}
void SetStatus(string s) { m_status = "CSDN通知 : " + m_name + s; } //具体设置状态信息
string GetStatus() { return m_status; }
};
//具体观察者
class ObserverBlog : public Observer
{
private:
string m_name; //观察者名称
Blog *m_blog; //观察的博客,当然以链表形式更好,就可以观察多个博客
public:
ObserverBlog(string name,Blog *blog): m_name(name), m_blog(blog) {}
~ObserverBlog() {}
void Update() //获得更新状态
{
string status = m_blog->GetStatus();
cout<<m_name<<"-------"<<status<<endl;
}
};
//测试案例
int main()
{
Blog *blog = new BlogCSDN("Blogger");
Observer *observer1 = new ObserverBlog("observer1", blog);
Observer *o2 = new ObserverBlog("observer2" , blog);
blog->Attach(observer1);
blog->Attach(o2);
blog->SetStatus("发表article");
blog->Notify();
blog->SetStatus("添加修改");
blog->Notify();
delete blog;
delete observer1;
return 0;
}
output:
observer1-------CSDN通知 : Blogger发表article
observer2-------CSDN通知 : Blogger发表article
observer1-------CSDN通知 : Blogger添加修改
observer2-------CSDN通知 : Blogger添加修改
template
#include<iostream>
using namespace std;
template <class T>//模板类
class Stack{
T pool[100];
int top;
public:
Stack():top(0){}//初始化!!!!
void Push(T i){
//isfull
pool[top++]=i;
}
T Pop(){
return pool[--top];//后++前--
}
};
//容器最适用于模板类型
int main (int argc,char*argv[]){
Stack<int> s;
for(int i=0;i<10;i++){
s.Push(i);
}
for(int i=0;i<10;i++){
cout << s.Pop() << endl;
}
}
STL
C++ STL(标准模板库)是一套功能强大的 C++ 模板类,提供了通用的模板类和函数,这些模板类和函数可以实现多种流行和常用的算法和数据结构,如向量、链表、队列、栈。
C++ 标准模板库的核心包括以下三个组件:
组件 | 描述 |
---|---|
容器(Containers) | 容器是用来管理某一类对象的集合。C++ 提供了各种不同类型的容器,比如 deque、list、vector、map 等。 |
算法(Algorithms) | 算法作用于容器。它们提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换等操作。 |
迭代器(iterators) | 迭代器用于遍历对象集合的元素。这些集合可能是容器,也可能是容器的子集。 |
*****************************iterator
迭代器是一个变量,相当于容器和操纵容器的算法之间的中介。迭代器可以指向容器中的某个元素,通过迭代器就可以读写它指向的元素。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v; //v是存放int类型变量的可变长数组,开始时没有元素
for (int n = 0; n<8; ++n)
v.push_back(n); //push_back成员函数在vector容器尾部添加一个元素
vector<int>::iterator i; //定义正向迭代器
for (i = v.begin(); i != v.end(); ++i) { //用迭代器遍历容器
cout << *i << " "; //*i 就是迭代器i指向的元素
*i *= 2; //每个元素变为原来的2倍
}
cout << endl;
//用反向迭代器遍历容器
for (vector<int>::reverse_iterator j = v.rbegin(); j != v.rend(); ++j)
cout << *j << " ";
return 0;
}
双冒号(::)用法
- 表示“域操作符”
- 例:声明了一个类A,类A里声明了一个成员函数void f(),但没有在类的声明里给出f的定义,那么在类外定义f时,就要写成void A::f(),表示这个f()函数是类A的成员函数。
- 直接用在全局函数前,表示是全局函数
- 例:在VC里,你可以在调用API 函数里,在API函数名前加::
- 表示引用成员函数及变量,作用域成员运算符
- 例:System::Math::Sqrt() 相当于System.Math.Sqrt()