cplusNote - juedaiyuer/researchNote GitHub Wiki

c++笔记

工程化

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没有哦...

初始化方法

  1. 复制初始化 int x = 1024; (C语言有的特性)
  2. 直接初始化 int x(1024);

随用随定义

  1. C语言:所有变量定义必须位于函数体的前面
  2. 随用随定义

输入输出方式

C

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;
}
  • 可以设置只读属性,类的封装不设置对外写接口

内联函数

  • 类内定义的函数优先选择编译为内联函数

内联函数

类外定义

  1. 同文件 类外定义 类名::成员{}
  2. 分文件类外定义 文件名 类名.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;   // 第二个单元

##深拷贝与浅拷贝##

浅拷贝

*两个实例化对象指向同一块内存,最后操作的对象总是覆盖掉前一对象的操作;释放内存时,能够导致内存的二次释放

深拷贝

  1. 重新申请内存
  2. 传入对象的内存赋值给新申请的内存,通常用遍历的方式来赋值

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;
}
  • 指针访问对象数组成员:指针名[偏移量].成员

测试

  1. &coorArr[0] 打印出来的结果是地址,coorArr[0]是一个数值嘛?
  2. 测试结果,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##

  1. const int * p = int const *p

const修饰的是(*p) , 即指针指向的数值,数值为常量

  1. int * const p

const修饰的是p, 指针为常量

  1. int x=3 const int &y = x x=0正确,y=20错误(const修饰了引用y,y为常量,不可以赋值)

  2. 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的限制

  1. 普通函数不能是虚函数
  2. 静态成员函数不能是虚函数
  3. 内联函数不能是虚函数,如果使用inline关键字失效
  4. 构造函数
  • 基类对象析构函数之前要用virtual修饰,防患于未然.

实现原理

  • 函数指针
  • 虚函数表指针->虚函数表->(偏移)->函数入口地址
  • 函数的覆盖与隐藏问题:子类重新定义了与父类完全相同的虚函数,覆盖掉了从父类继承而来的函数地址,变成子类的函数地址

执行完子类的析构函数就会执行父类的析构函数

纯虚函数

  • virtual 返回值类型 成员函数() = 0;
  • 含有纯虚函数的类叫做抽象类
  • 抽象类无法实例化对象,只有纯虚函数在子类定义实现,方能够实例化
  • 抽象类的子类也能是抽象类

虚函数表

-对象大小,地址,成员函数地址,虚函数表指针

  • 一个数据成员没有的类,实例化的对象占据一个内存单元;如果有数据成员,对象大小由数据成员而定
  • 虚函数表指针,一个占据4个字节,实例化对象的时候内存地址在一个对象的前面

接口

  • 类定义中仅有纯虚函数
  • 接口类更多的表达了一种能力和协议

运行中识别(RTTI)

  • 父类指针指向子类对象,执行子类对象特有的方法

dynamic_cast

  1. 只能用于指针与引用之间的转换
  2. 要转换的类型中必须包含虚函数
  3. 转换成功返回子类的地址,失败返回NULL

typeid

  1. 返回一个type_info对象的引用
  2. 如果想通过基类的指针获得派生类的数据类型,基类必须带有虚函数
  3. 只能获取对象的实际类型

异常处理

  • try-catch,throw
  • Exception 异常处理的基类,常用于接收异常信息,父类指向子类对象的一个例子

友元

  • 关键字 friend
  • 友元全局函数
  • 友元成员函数 friend 返回类型 另一个类名::成员函数();
  • 可以访问私有成员,不同类之间的访问,对象与类数据的访问
  • 破坏了数据的封装性

注意事项

  1. 友元关系不可传递
  2. 友元关系的单向性
  3. 友元声明的形式及数量不受限制

静态

static

  • 静态数据成员在不实例化的时候,依然占据内存
  • 初始化: 类型 类名::成员数据 = num;
  • 静态无法调用普通成员
  • 静态在不实例化的时候,依然能够访问

访问

  1. 类名::成员
  2. 对象.成员

重载

  • 给原有的运算符赋予新的功能,本质:函数的重载
  • 关键字: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;

source

  • 慕课网 C++远征攻略
⚠️ **GitHub.com Fallback** ⚠️