cplusNote - juedaiyuer/researchNote GitHub Wiki
工程化
A.h
成员函数1;
成员函数2;
A.c
#include"A.h"
A::成员函数1{}
A::成员函数2{}
demo.c
#include"A.h"
int main()
{
todo...
}
相比于C,C++的新特性
- C++有布尔类型,C没有哦...
初始化方法
- 复制初始化 int x = 1024; (C语言有的特性)
- 直接初始化 int x(1024);
随用随定义
- C语言:所有变量定义必须位于函数体的前面
- 随用随定义
输入输出方式


-
不用关心占位符 ;譬如 %d ,%f ...
-
不用关心数据类型
cout << oct|dec|hex|boolalpha << x <<endl; //输出8进制 | 10进制 | 16进制 | 布尔类型
namespace
- 给程序分屋子并且取名,解决名字冲突问题
- 调用: 命名::成员
string类型
-
包含在头文件 ; 命名空间std下
string name = "rose"; string name("rose"); string s1(name); string s2(n,'c')
string s = "hello"+"world"; //非法操作?
getline(cin,name);
##OOP##
数据的封装
class Student
{
public:
void setAge(int _age){age = _age;}
int getAge(){return age;}
private:
string name;
int age;
}
- 可以设置只读属性,类的封装不设置对外写接口
内联函数
- 类内定义的函数优先选择编译为内联函数

类外定义
- 同文件 类外定义 类名::成员{}
- 分文件类外定义 文件名 类名.h 调用者: #include"类名.h" 类名::成员{}
##构造函数##
- 构造函数在对象实例化时被自动调用,且仅调用一次
- 构造函数与类同名
- 构造函数没有返回值
- 构造函数可以有多个重载形式
- 实例化对象时仅用到一个构造函数
- 当用户没有定义构造函数时,编译器自动生成一个构造函数
初始化列表
student():m_strName("rose"),m_iAge(20){}
- 初始化列表先于构造函数执行
- 初始化列表只能用于构造函数
侯捷老师提到过推荐使用初始化列表
-
初始化列表可以征服const声明的常量,个人感觉就像先执行初始化列表,进行变量m_Age的初始化,然后const修饰将变量转换成常量
person(int age):m_Age(age){}
private: const int m_Age;
拷贝构造函数
定义
类名(const 类名 &变量名)
- 没有定义拷贝构造函数,系统会自动生成一个默认的拷贝构造函数
- 直接初始化,复制初始化,系统自动调用拷贝构造函数
整个对象的拷贝
##析构函数##
~类名(){}
对象数组
Coordinage coor[3] //栈实例化对象数组
coor[0].m_iX //访问成员
Coordinate *p = new Coordinate[3]; //堆实例化对象数组
p[0].m_iX // p->m_iX
p++;
P->m_iX; // 第二个单元
##深拷贝与浅拷贝##
浅拷贝
*两个实例化对象指向同一块内存,最后操作的对象总是覆盖掉前一对象的操作;释放内存时,能够导致内存的二次释放
深拷贝
- 重新申请内存
- 传入对象的内存赋值给新申请的内存,通常用遍历的方式来赋值
Test(const Test& arr){
m_pArr = new int[m_iCount];
for(int i=0;i<m_iCount;i++)
{
m_pArr[i] = arr.m_pArr[i];
}
}
##对象指针##
单一对象
Coordinate *p = new Coordinate;
p->member;
(*p).member; //这个最是让自己匪夷所思的,p指向了Obj,*p的意思就是读取了Obj
对象数组指针
#include <iostream>
using namespace std;
class Coordinate
{
public:
Coordinate()
{
}
// 打印坐标的函数
void printInfo()
{
cout<<"("<<m_iX<<","<<m_iY<<")"<<endl;
}
public:
int m_iX;
int m_iY;
};
int main(void)
{
//定义对象数组
Coordinate *coorArr = new Coordinate[2];
coorArr[0].m_iX = 1;
coorArr[0].m_iY = 2;
coorArr[1].m_iX = 3;
coorArr[1].m_iY = 4;
//遍历数组,打印对象信息
for(int i = 0; i < 2; i++)
{
coorArr[i].printInfo();
}
return 0;
}
- 指针访问对象数组成员:指针名[偏移量].成员
测试
- &coorArr[0] 打印出来的结果是地址,coorArr[0]是一个数值嘛?
- 测试结果,error,姑且就把它当作对象看待
##对象成员指针##
class Line
{
public:
Line();
~Line();
private:
Coordinate *m_pCoorA;
Coordinate *m_pCoorB;
}
Line::Line()
{
m_pCoorA = new Coordinate(1,3);
m_pCoorB = new Coordinate(5,6);
}
Line::~Line()
{
delete m_pCoorA;
delete m_pCoorB;
}

##this指针##
-
解决参数与数据成员同名
-
this指针在参数列表中的位置
return (*this) //返回一个对象
-
返回引用对象,比如第一次操作改变了成员变量,第二次操作 返回对象.成员变量,变量会发生改变
-
如果直接返回一个对象,第二次操作不会改变变量
this指针的特殊用法
#include<iostream>
using namespace std;
class Array
{
public:
Array(int len):m_len(len)
{
cout<<"构造函数"<<endl;
}
void set(int len)
{
m_len = len;
}
int get()
{
return m_len;
}
Array printinfo()
{
cout<<"m_len= "<<m_len<<endl;
return (*this);
}
private:
int m_len;
};
测试程序1
int main()
{
Array arr1(10);
arr1.printinfo().set(5);
cout<<arr1.get()<<endl;
return 0;
}
- 测试结果arr1的数值没有发生改变,因为返回的另外一个对象,该对象为临时对象
解决方法
Array& printinfo()
{
cout<<"m_len= "<<m_len<<endl;
return (*this);
}
- 将返回对象修改为返回引用,指向同一个内存区域,就可以测试成功
返回对象修改为对象指针
Array* printinfo()
{
cout<<"m_len= "<<m_len<<endl;
return this;
}
int main()
{
Array arr1(10);
arr1.printinfo()->set(5);
cout<<arr1.get()<<endl;
return 0;
}
##const##
- const int * p = int const *p
const修饰的是(*p) , 即指针指向的数值,数值为常量
- int * const p
const修饰的是p, 指针为常量
-
int x=3 const int &y = x x=0正确,y=20错误(const修饰了引用y,y为常量,不可以赋值)
-
int x =3 (变量) const int x= 3 (常量)
- 如果成员变量前const修饰,那么构造函数在函数体内定义,这样的方法会产生error;但是倘若使用初始化列表的方法的话,程序会成功,初始化列表完成了成员变量的初始化,然后const修饰,之后成员变量为常量,不可进行赋值操作了.
- 常对象成员,也可以用初始化列表这样的技术手段
常成员函数
函数 const;
-
常成员函数中不能改变成员变量的数值
void change() const { m_iX = 10; }
void change(const *this) { this->m_iX = 10; //error const修饰了(*p)指向的数值,数值为常量 }
//互为重载 void changeX() const; void changeX();
-
调用的时候需要前面const来区分,否则调用普通函数,而不是常成员函数
-
常成员函数的本质是内部使用常this指针
-
常成员函数内不能调用普通的成员函数
常指针与常引用
int main(void)
{
Coordinate coor1(3,5);
const Coordinate &coor2 = coor1;
const Coordinate *pCoor3 = &coor1;
coor1.printInfo();
coor2.getX(); //error getX()要求传入读写权限的参数,而coor2传入的this指针只有读权限
pCoor3->getY(); //error ...同上
return 0;
}
- getX, getY为普通成员函数,有着读写权限.
##继承##



- 继承,函数体内的成员函数访问(public,private,protected),实例化对象访问(public),搞清楚这些就没有问题了
- 隐藏,子类出现与父类同名的函数,可以通过特殊手段访问隐藏的父类成员
- 访问时:对象名.父类名::成员
- 当子类函数名相同,参数不同,能否利用重载特性来调用父类和子类呢?
- 不能,只能进行隐藏,不能利用重载特性,调用依然采用 对象名.父类名::成员
is-A
例子程序,基类Person,派生类Soldier
int main()
{
Soldier s1;
Person p1 = s1;
Person *p2 = &s1;
//错误代码
s1 = p1;
Soldier *s2 = &p1;
return 0;
}
-
子类可以赋值给父类
-
基类的指针,引用可以接收子类对象
void fun1(Person *p){} void fun2(Person &p){}
int main() {
Person p1; Soldier s1; fun1(&p1); fun1(&s1); fun2(p1); fun2(s1);}
-
内存解释,子类数据丢失(父类 = 子类)
-
父类指针,只能指向自己的那部分,子类独有的部分不能指向
Person *p = new Soldier;
-
当父类指针指向堆中子类对象,构造函数调用顺序,父类->子类 析构函数只执行父类析构,内存泄漏.采用需析构函数的技术手段
虚析构函数
virtual ~Person(); //父类析构,可以继承到子类
- delete指针可以释放掉内存
- 析构函数执行顺序:1.子类 2.父类
临时对象的销毁机制,影响了程序的效率;所以传递对象参数的时候一般选择引用传递,指针传递
test(Person &p){}
test(Person *p){}
多重继承
A->B->C
多继承
子类对象有多个父类
public:A,public:C...
虚继承

- 解决继承手法带来的数据冗余问题
- 重定义问题的解决方式
- 菱形继承问题当中,重定义经常遇到,使用宏定义
#ifdef 头文件 #define 头文件 . . . #endif
虚继承的自我理解
A : a() B : public A {a()} C : public A {a()} D : public B,public C
- D的实例化对象,想要用到a(),但是B,C两个类都有a()方法,于是矛盾产生了.
- 当然页可以采用 B::a() ,C::a()这样的方式进行调用
##多态##
静态多态-早绑定
动态多态-晚绑定
- 情景描述:父类子类有完全一样的成员函数
- 父类指针指向子类对象,父类成员函数a(),子类也有一个成员函数a(),这个时候实例化对象访问a(),只能实现父类的成员函数.释放内存的时候,delete 父类指针 , 仅释放掉父类.
- virtual a(),晚绑定可以解决这样的问题,但仍存在释放内存问题
- vitual方法实现了父类指针调用子类方法
虚析构函数
- 多态技术,如果进行了申请内存的操作,那么就会出现内存泄漏问题.父类指针释放时,只执行父类的析构函数;子类对象的指针,即执行子类的析构,也执行父类的析构函数.
virtual的限制
- 普通函数不能是虚函数
- 静态成员函数不能是虚函数
- 内联函数不能是虚函数,如果使用inline关键字失效
- 构造函数
- 基类对象析构函数之前要用virtual修饰,防患于未然.
实现原理
- 函数指针
- 虚函数表指针->虚函数表->(偏移)->函数入口地址
- 函数的覆盖与隐藏问题:子类重新定义了与父类完全相同的虚函数,覆盖掉了从父类继承而来的函数地址,变成子类的函数地址
执行完子类的析构函数就会执行父类的析构函数
纯虚函数
- virtual 返回值类型 成员函数() = 0;
- 含有纯虚函数的类叫做抽象类
- 抽象类无法实例化对象,只有纯虚函数在子类定义实现,方能够实例化
- 抽象类的子类也能是抽象类
虚函数表
-对象大小,地址,成员函数地址,虚函数表指针
- 一个数据成员没有的类,实例化的对象占据一个内存单元;如果有数据成员,对象大小由数据成员而定
- 虚函数表指针,一个占据4个字节,实例化对象的时候内存地址在一个对象的前面
接口
- 类定义中仅有纯虚函数
- 接口类更多的表达了一种能力和协议
运行中识别(RTTI)
- 父类指针指向子类对象,执行子类对象特有的方法
dynamic_cast
- 只能用于指针与引用之间的转换
- 要转换的类型中必须包含虚函数
- 转换成功返回子类的地址,失败返回NULL
typeid
- 返回一个type_info对象的引用
- 如果想通过基类的指针获得派生类的数据类型,基类必须带有虚函数
- 只能获取对象的实际类型
异常处理
- try-catch,throw
- Exception 异常处理的基类,常用于接收异常信息,父类指向子类对象的一个例子
友元
- 关键字 friend
- 友元全局函数
- 友元成员函数 friend 返回类型 另一个类名::成员函数();
- 可以访问私有成员,不同类之间的访问,对象与类数据的访问
- 破坏了数据的封装性
注意事项
- 友元关系不可传递
- 友元关系的单向性
- 友元声明的形式及数量不受限制
静态
static
- 静态数据成员在不实例化的时候,依然占据内存
- 初始化: 类型 类名::成员数据 = num;
- 静态无法调用普通成员
- 静态在不实例化的时候,依然能够访问
访问
- 类名::成员
- 对象.成员
重载
- 给原有的运算符赋予新的功能,本质:函数的重载
- 关键字:operator
一元运算符
成员函数的重载
Coordinate & operator-()
{
this->m_iX = -m_iX;
this->m_iY = -m_iY;
return *this;
}
-coor1; // coor1.operator-();
友元函数的重载
貌似是友元函数参数里面没有默认的this指针要传递,所有用到了引用传递参数
friend Coordinate& operator(Coordinate& e)
{
e.m_iX = -e.m_iX;
e.m_iY = -e.m_iY;
return e;
}
前置++定义
Coordinate & operator++()
{
m_iX++;
m_iY++;
return *this;
}
后置++定义
Coordinate operator++(int) //采用标志位的方式
{
Coordinate old(*this);
this->m_iX++;
this->m_iY++;
return old;
}
二元运算符
Coordinate operator + (const Coordinate &coor)
{
Coordinate temp;
temp.m_iX = this->m_iX + coor.m_iX;
temp.m_iY= this->m_iY + coor.m_iY;
return temp;
}
coor3 = coor1 + coor2; //coor1.operator+(coor2)
友元函数的重载
friend Coordinate operator + (const Coordinate& coor1,const Coordinate& coor2)
{...}
coor3 = coor1 + coor2; // operator+(coor1,coor2)
<<运算符
#include<ostream>
class Coordinate
{
friend ostream& operator<<(ostream &out,const Coordinate &coor)
{
out<<coor.m_iX<<","<<coor.m_iY;
return out;
}
}
cout << 对象名
- 输出运算符仅能使用友元函数重载
[]索引运算符仅能使用成员函数重载
模板
-
比较两个数字的大小,逻辑相同,仅是数据类型不一样
-
模板技术将类型作为参数
-
关键字:template typename class
template T max(T a,T b) { return (a>b)?a:b; }
int ival = max(100,99); //不指定类型,编译器自行判断 char cval = max('A','B');
变量作为模板参数
template <int size>
void display()
{
cout<<size<<endl;
}
display<10>();
多参数函数模板
template <typename T,typename C>
template <typename T,int size>
void display(T a)
{
for(int i=0;i<size;i++)
cout<<a<<endl;
}
display<int,5>(15);
类模板
-
模板代码不能分离编译,h文件与c文件的分离
-
类外定义
template<...> 返回数据类型 类名<...>::成员函数
标准函数库
-
使用STL的时候,不要忘记相应的include哦...
-
迭代器相对应的是begin(),end();数据成员相对应的是front(),
-
vector
-
list[i] 无法使用,需要使用迭代器遍历
-
map 没有push_back方法,方法为insert
map<int,string> m; pair<int,string> p1(3,"rose"); m.insert(p1);
cout<<m[3]<<endl; //通过索引来访问数据
普通方式
for(int k = 0;k<vec.size();k++)
{
cout<<vec[k]<<endl;
}
迭代器方式
vector<string>::iterator citer = vec.begin();
for(;citer!=vec.end();citer++)
cout<<*citer<<endl;
- 慕课网 C++远征攻略