Cplusplus base01

关键字

namespace

namespace通常用来给类或者函数做个区间定义,以使编译器能准确定位到适合的类或者函数.


auto

auto可以在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型.


const

用法1:const修饰函数传入参数 指针或是引用时便是不允许修改

用法2:修饰函数返回值 可以阻止用户修改返回值,返回值也要相应的付给一个常量或常指针。

用法3:const修饰成员函数(c++特性)

const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数;

const对象的成员是不能修改的,而通过指针维护的对象确实可以修改的;

const成员函数不可以修改对象的数据,不管对象是否具有const性质。编译时以是否修改成员数据为依据进行检查。


virtual

c++中的关键字“virtual”主要用在两个方面:虚函数与虚基类。

用于虚函数: 虚函数源于c++中的类继承,是多态的一种.且继承的子类必须实现父类中的虚函数.非虚函数是静态编译的时候就已经生成了,虚函数由运行时决定.

内联(inline)函数不能是虚函数,因为内联函数不能在运行中动态确定位置。

C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数

虚函数(Virtual Function)是通过一张虚函数表来实现的。每个父类都有自己的虚表.在表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中.

虚函数其他

1.虚函数是可以递归调用的;

构造函数和析构函数中是可以调用析构函数的.但都不会触发多态.

构造函数中调用析构函数不会触发多态.此时子类还没有构造,所以此时的对象还是父类的,不会触发多态。

析构函数中调用虚函数不会触发多态.析构函数也是一样,子类先进行析构,这时,如果有virtual函数的话,子类的内容已经被析构了,C++会视其父类,执行父类的virtual函数。


inline

内联函数和普通函数的区别在于:当编译器处理调用内联函数的语句时,不会将该语句编译成函数调用的指令,而是直接将整个函数体的代码插人调用语句处.内联函数的调用不需要付出执行函数调用的额外开销.


explicit

默认类构造函数使隐式(默认有 implicit 关键字修饰)的即 Mystring str = "str" 编译器会自动转换为 Mystring str("str") explicit关键字的作用就是防止类构造函数的隐式自动转换. 被explicit修饰后的构造函数将不再允许隐式转换 只能显式调用Mystring str("str").

[!注意:explicit 只在修饰单参数构造函数才有效果.]

返回对象的函数不调用拷贝构造函数?

拷贝构造函数前加 “explicit” 关键字;这样拷贝构造函数就不会进行自动类型转换,也就不会实现拷贝构造函数的调用了


export[扩展]

对模板类型,则必须在定义这些模板类对象和模板函数时,使用标准 C++ 新增加的关键字 export(导出)。


mutable[扩展]

inline函数与有参宏

inline C有参宏的替代


指针与引用

指针与引用区别

指针 :指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;指针可以有二级指针引用没有,指针可以不赋初值;引用必须有明确引用对象.作为函数参数是传递进来的是实参的地址.

引用原变量的别名;作为函数参数时传递进来的是实参本身.

指针引用


NULL与nullptr 区别

NULL

在标准C库文件定义中 NULL 本质是一个宏 其定义的值为 0

nullprt C++11

指针字面量 nullptr C++11 中的关键词 C++11起 将NULL 宏定义为了 nullptr

const sizeof const与引用

this指针

this 是一个 const 指针,是一个隐式形参,C++中的关键字,它指向当前对象,通过它可以访问当前对象的所有成员。函数中,成员变量和形参同名时(this->name = name),只能通过过指针区分.否则将会出现将成员变量值赋给形参这一情况.

this 只能在成员函数内部使用,用在其他地方没有意义,也是非法的.只有当对象被创建后 this 才有意义,因此不能在 static 成员函数中使用.

什么是面向对象

面向对象是一种对现实世界理解和抽象的方法、是通过将需求要素转化为对象进行问题处理的一种思想。面向对象的三个基本特征是:封装、继承、多态。

封装

(1)封装:封装是实现面向对象程序设计的第一步,封装就是将数据或函数等集合在一个个的单元中(我们称之为类)。封装的意义在于保护或者防止代码(数据)被我们无意中破坏。

继承

(2)继承:继承主要实现重用代码,节省开发时间。子类可以继承父类的一些东西。

封装(pubilc protect private)

封装:!!!隐藏对象的属性和实现细节!!!,仅对外公开接口和对象进行交互,将数据和操作数据的方法进行有机结合.

注意:访问限定符本质上是给编译器使用的,数据放在内存中是没有任何限制的.


继承(多路径继承、虚继承[虚函数 纯虚函数 虚基类 虚析构的作用])

继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有的特性基础上进行扩展,增加功能,这样产生新的类,称作是派生类.继承是类设计层次的复用.

继承的三种方式

公有继承(public):

父类公有仍为公有 - 父类保护仍是保护 - 父类私有仍是是私有,且基类不可访问.

保护继承(protected):

父类公有变保护 - 父类保护仍是保护 - 父类私有仍是私有,且基类不可访问.

私有继承(private):

父类公有变私有 - 父类保护变私有 - 父类私有仍是私有,且基类私有不可访问.

多继承

多继承注意二义性问题.存在父类也存在继承时,子类访问爷爷辈的基类成员要用域运算符指明访问的时父类的成员还是爷爷辈的成员 如

A(_a : int)->B(_a : int)->C; 
C(a : int){_a = a}; //这句换会报错, 产生歧义不知道访问的时 B 基类中的 属性_a 还是 A基类中的 属性_a 需要用域运算符指明.
C(a : int){A::_a = a};// 正确写法

虚继承

为了解决多继承时的命名冲突//二义性问题 和冗余数据问题,虚继承的最终派生类中只保留了一份虚基类的成员,所以该成员可以被直接访问,不会产生二义性.不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员.

虚函数作用

1.父类的情况:作为已定义的接口,强制子类必须实现.

2.作为虚析构函数,有父类指针持有在堆上的子类实例时,将父类析构函数设为虚析构函数能确保子类正确析构,防止内存泄漏.若父类虚构函数不是虚析构函数在子类析构时,只是虚构了父类,子类却不会正确虚构,这将导致内存泄漏.-- 解决办法,将父类析构函数改为虚析构函数.

虚基类中的虚函数表指针

若父类有虚函数,则在对象占用的内存块中的最前放还会有一个隐藏的虚基类表指针(虚函数表指针存储着虚函数表的地址),这是 多态(虚函数) 的实现关键.

多态正式由虚基类表指针指向的虚函数表所实现.

虚函数表

虚函数表是一块连续的内存空间,存储的是虚函数的函数入口地址.

虚基类的继承是多态的确切实施步骤,子类通过继承同样也会拥有了一个虚函数表指针,一个独立的虚函数表,在不重写的情况下,子类虚函数表所存储的函数入口地址与父类的表相同.若有重写,则会由重写的函数地址覆盖原父类函数入口的地址.

Class Base
{
pubilc:
int a = 1;

public:
virtual Func()
{
	// std::cout<<"Base Func. a = "<<a<<std::endl; // 通过函数指针直接持有表上函数地址是,是不带由实例属性的,因此不是实例调用函数.此句话 通过函数指针直接持有表上函数地址来直接调用词函数将会报错.
	std::cout<<"Base Func."<<std::endl;

}

Class InstanceA :public Base
{
public:
InstanceA():a(2){}
virtual Func()
{
	// std::cout<<"InstanceA Func. a = "<<a<<std::endl;// 通过函数指针直接持有表上函数地址是,是不带由实例属性的,因此不是实例调用函数.此句话 通过函数指针直接持有表上函数地址来直接调用词函数将会报错.
	std::cout<<"InstanceA Func."<<std::endl;
	}
}

typedef void (*Func)() ;

Base base;
InstanceA instancea;

int* p = (int*)&base;
int* p1 = (int*)&instancea;

Func Funcptr_Base = (Func)*((int*)*p);
Func Funcptr_InstanceA = (Func)*((int*)*p1);

Funcptr_Base(); // call Base::Func();
Funcptr_InstanceA(); // call InstanceA::Func();

什么是多态

基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式,我们将这种现象称为多态.

运行时多态

友元与运算符重载

友元

友元函数: 一个类的友元函数可以访问该类的私有成员.

友元类: 若A是B的友元类,则A的成员函数可以访问B的私有成员

运算符重载为友元函数

!!![运算符为类的重载成员函数时不能满足需求,重载为普通函数又不能访问类的私有成员]!!!,此时需要将运算符重载为友元.

类拷贝(深拷贝、浅拷贝)

拷贝构造函数

第一个参数是自身类类型引用,其他参数都有默认值的构造函数就是拷贝构造函数.拷贝构造函数自己的参数必须是引用类型.

Sales_data::Sales_data(const Foo&){};

浅拷贝 (浅拷贝也叫位拷贝,拷贝的是地址)

浅拷贝在类成员中没有指针类型的成员时可以正常使用,但在有指针类型的成员时,浅拷贝中当前实例的指针类型成员若是直接由传入对象中的指针直接赋值,则会出现两个指针指向同一块内存的现象,通过其中一个指针去释放这块内存是另一指针将会变成野指针.

深拷贝 (深拷贝也叫值拷贝,拷贝的是内容)

深拷贝是为了避免在浅拷贝中两指针直接赋值使得两指针指向同一块地址这一问题(因为当通过其中一个指针释放指向的内存块时,另一指针将会成为野指针.),即新开辟一块内存将当前指针指向这块内存,并将传入对象中指针指向的内存中的内容也拷贝一份给当前指针指向的内存块.

// 深拷贝构造函数
Array::Array(const Array& rhs) {
		m_iCount = rhs.m_iCount;
		m_pArr = new int[m_iCount];
		for (int i = 0; i < m_iCount; i++)
		{
			m_pArr[i] = rhs.m_pArr[i];
		}
}

C++智能指针

智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。

为什么要使用智能指针?

智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针是一个类,当超出了类的实例对象的作用域时,会自动调用对象的析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

auto_ptr(C++11已弃用)

C++98 中的解决方案,采用所有权模式.有内存泄露的潜在风险.

auto_ptr<string> p1 (new string ("I reigned lonely as a cloud.")); 
auto_ptr<string> p2; 
p2 = p1; //auto_ptr不会报错.

此时不会报错,p2 剥夺了 p1 的所有权,但是当程序运行时访问 p1 将会报错。所以 auto_ptr 的缺点是:存在潜在的内存崩溃问题!

share_ptr(共享指针)

shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存.

多个share_ptr可以指向同一个对象.循环引用会出现内存泄漏.即两个 shared_ptr 相互引用, 那么这两个指针的引用计数永远不可能下降为 0, 资源永远不会释放。

ptr_a-> m_bb_ptr = ptr_b;
ptr_b->m_aa_ptr = ptr_a;

unique_ptr(独占指针、同种有互斥属性)

unique_ptr “唯一 ” 拥有其所指对象,同一时刻只能有一个unique_ptr实例指向给定对象(通过禁止拷贝、只有移动所有权move()函数来实现)。

weak_ptr

weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象.weak_ptr 是用来解决 shared_ptr 相互引用时的死锁问题.弱指针不会增加指向对象的引用计数,也不会对引用对象的内存进行管理,但弱引用指针会检测所管理的对象是否已经被正确释放.

class B;	//声明
class A
{
public:
	shared_ptr<B> pb_;
	~A()
	{
		cout << "A delete\n";
	}
};

class B
{
public:
	shared_ptr<A> pa_;
	~B()
	{
		cout << "B delete\n";
	}
};

void fun()
{
	shared_ptr<B> pb(new B());
	shared_ptr<A> pa(new A());
	cout << pb.use_count() << endl;	//1
	cout << pa.use_count() << endl;	//1
	pb->pa_ = pa;
	pa->pb_ = pb;
	cout << pb.use_count() << endl;	//2
	cout << pa.use_count() << endl;	//2
}

int main()
{
	fun();
	// fun 函数结束时意味着 类A B的实例生命周期已结束
	// 但由于 循环引用 使得二者的引用计数 仍不为0 所以 A B的实例并未正确析构.

	//解决办法:
	// 将类 A 或者B中的共享指针share_ptr 改为 弱引用指针 weak_ptr.
	return 0;
}

原理:

  对于弱引用来说, 当引用的对象活着的时候弱引用不一定存在. 仅仅是当它存在的时候的一个引用, 弱引用并不修改该对象的引用计数, 这意味这弱引用它并不对对象的内存进行管理.
  weak_ptr 在功能上类似于普通指针, 然而一个比较大的区别是, 弱引用能检测到所管理的对象是否已经被释放, 从而避免访问非法内存。

C++11四种类型转换(static_cast dynamic_cast const_cast reinpreter_cast)

const_cast

去const 属性.解除const,volatile修饰符,只能转换指针或者引用.

static_cast

静态转换,对应于C的隐式转换,但转换双方必须在一个层级,比如 变量和指针间转换 (变量和指针不在同一层级).

dynamic_cast

有条件转换,动态类型转换,运行时检查类型安全(转换失败返回NULL).

1.用于基类和子类之间的安全转换.

2.必须有虚函数.

3.同基类的子类间的交叉转换,但结果返回空.

reinterpret_cast

将一种类型转化为另一种不相关的类型,对应C的强制转换.

Tip:强制转换的代码是不可移植的,而且有时会产生不确定的结果.

语法格式

TYPE B = x_cast(TYPE)(a);

class与struct区别

C语言中struct

struct没有继承. 不能有成员函数.不能有静态成员.struct 默认访问控制属性 public 且不能修改 而 class 默认访问控制属性时 private.不能在结构体中直接初始化数据成员.

static与this指针

没有静态的this指针

STL使用

STL主要有什么

STL包括6部分内容:
(1)容器(containers):是一种数据结构容器,使用类模板的方式提供,我们可以方便的进行数据的存储操作。STL中,容器分为两类:顺序容器和关联式容器。

(2)适配器(adapters):以序列式容器为基础,提供的栈,队列和优先级队列的这种容器。---也是一种容器

(3)迭代器(iterators):类似于指针,用来操作容器的对象。

(4)算法(algorithm):包含一系列的常见算法。

(5)空间配置器(allocator):其中主要工作包括两部分:1,对象的创建与销毁。2,内存的创建与释放。

(6)仿函数(functor):仿函数又称为函数对象,其实就是重载了()操作符的struct,没有什么特别的地方。---一大堆重载的操作符

set:其内部元素会根据元素的键值自动被排序。区别于map,它的键值就是实值,而map可以同时拥有不同的键值和实值。

Vector 扩容

vector

它是一个动态分配存储空间的容器。区别于c++中的array,array分配的空间是静态的,分配之后不能被改变,而vector会自动重分配(扩展)空间。

vector扩容 1.5或者2倍扩容(扩容因子:1.5或2)

vector::resize(size_t n); 指定的vector中当前的可容纳元素数量小于原来的容量则删除末尾部分.

vector::reserve(size_t n) reserve方法被用来重新分配vector的容量大小;

iterator erase(iterator) 后返回下一个

迭代器提供了一种方法,使它能够通过迭代器访问容器所含的各个元素,但无需暴露该容器的内部结构,它将容器和算法分开,好让而这独立设计.

map

std::map 是有序键值对容器,它的元素的键是唯一的.map 通常实现为红黑树.

set

std::set 是关联容器,含有 Key 类型对象的已排序集.set 通常以红黑树实现. !!![std::set是一颗封装好的封装好的红黑树]!!!.

map/set list/vector 使用场景

map/set

查找较多 其他操作较少时用 map(数据类型为key-value时)/set(只有values的场景)

list/vector

list

list 随机访问性能较差 但随机删除插入操作较快. 适用于 随机访问较少但其他操作较多的情况

vector

vector支持按矢访问 随机访问性能较高 但非向量头或尾部位置的插入删除操作较慢. 适合在随机访问频率较高但随机插入删除较少的情况下使用.

STL源码[扩展]

new malloc alloc

delete free

string类实现

Tip 注意几个友元操作符的重载. 比较运算符 == != > < 加号运算符 + 流入流出运算发 << >>

friend bool operator==(const MyString &str1,const MyString &str2);
friend bool operator==(const char *str,const MyString &str2);
friend bool operator==(const MyString &str1,const MyString *str2);
friend bool operator>(const MyString &str1,const MyString &str2);
friend bool operator>(const char*str1,const MyString &str2);
friend bool operator>(const MyString &str1,const char*str2);

friend MyString operator+(const MyString &str1,const MyString &str2);
friend MyString operator+(const char*str1,const MyString &str2);      //两个字符串进行相加
friend MyString operator+(const MyString &str1,const char*str2);
 
friend ostream & operator<<(ostream &os,const MyString &str);
friend std::istream & operator>>(std::istream & is, String & s);

C++ 特性

构造闭包:能够捕获作用域中的变量的匿名函数对象。

[ /*捕获*/ value_name  = & this] ( 形参 ) -> ret { 函数体 }

C++ 编码规范(Google编码规范)[了解]

Boost库[扩展]

Boost C++ 库 是一组基于C++标准的现代库。