文章目录
前言
c++文章连载:
1.C++基础
1.C++基础
2.C++新增和有变化的关键字
3.C++的内存管理
2.面向对象
1.C++的封装和访问权限
2.C++继承和多态特性
3.C++的运算符重载
4.C++静态类和静态成员
5.C++的友元函数和友元类
3.模板编程和STL
1.C++模板编程入门
2.STL的容器类和迭代器
3.STL的泛型算法
4.模板特化与类型萃取
5.STL的其他容器讲解
6.智能指针与STL查漏补缺
4.杂项
1.c++各种流操作
2.依赖,关联,聚合,组合,继承
3.一些技巧性的代码设计
1.典型的C++面向对象编程
1.1、元素
(1)头文件hpp中类的定义
(2)源文件cpp中类的实现(构造函数、析构函数、方法)
(3)主程序
1.2、C++面向对象式编程总结
(1)整个工作分为2大块:一个是建模和编写类库,一个是使用类库来编写主程序完成任务。
(2)有些人只负责建模和编写类库,譬如开发opencv的人。
(3)有些人直接调用现成类库来编写自己的主任务程序,譬如使用opencv分析一张图片中有没有电动车
(4)难度上不确定,2个都可能很难或者很简单。
2.C++的构造函数和析构函数
3.1、什么是构造函数
(1)constructor,字面意思是用来构造对象的函数;destructor,字面意思是用来析构对象的函数
(2)可以理解为语言自带的一种hook函数(回调函数:事先提供好了就不用管了,到了特定的条件就会被自动调用)
(3)当对象产生时constructor会自动被调用,一般用于初始化class的属性、分配class内部需要的动态内存
(4)对对象消亡时destructor会自动被调用,一般用于回收constructor中分配的动态内存,避免内存丢失
3.2、构造和析构一般用法
(1)不写时C++会自动提供默认的构造和析构,也可以显式提供默认构造和析构函数
(2)构造和析构函数不需要返回值类型,构造函数可以带参或不带参,析构函数不带参
(3)构造函数可以重载(overload),析构函数不需要重载
3.4、为什么需要构造函数和析构函数
(1)构造函数可以看作是对象的初始化式,注意对比对象和变量的初始化区别
(2)构造函数可以为对象完成动态内存申请,同时在析构函数中再释放,形成动态内存的完整使用循环。
(3)C语言中struct无构造函数概念,所以struct中需要用到动态内存时必须在定义struct变量后再次单独申请和释放,而这些操作都需要程序员手工完成
(4)C++ class的构造和析构特性,是C++支持面向对象编程的一大语言特性。
person.hpp:
#ifndef __PERSON_H__
#define __PERSON_H__
#include <string>
using namespace std;
namespace MAN
{
class person;
// 声明这个类
class person
{
// 访问权限
public:
// 属性
string name; // 名字
int age; // 年龄
bool male; // 性别,男为true,女为false
char *p; // 只是分配了p本身的4字节内存,并没有分配p指向的空间内存
// 构造和析构
person(); // 默认构造函数
person(string name); // 自定义构造函数
~person(); // 默认析构函数
// 方法
void eat(void);
void work(void);
void sleep(void);
private:
};
/*
// class的成员函数中可以引用class的成员变量,但是要考虑public和private这些
void inline MAN::person::eat(void)
{
cout << name << " eat" << endl;
}
*/
} // end of namespace MAN
#endif
person.cpp:
#include "person.hpp"
#include <iostream>
using namespace std;
// class的成员函数中可以引用class的成员变量,但是要考虑public和private这些
void MAN::person::eat(void)
{
cout << name << " eat" << endl;
}
void MAN::person::work(void)
{
// cout << "person work" << endl;
if (this->male)
cout << this->name << " coding" << endl;
else
cout << this->name << " shopping" << endl;
}
void MAN::person::sleep(void)
{
cout << this->name << " sleep" << endl;
}
MAN::person::person()
{
// 默认构造函数是空的
cout << "default constructor" << endl;
}
MAN::person::person(string name)
{
this->name = name; // 构造对象后,同时对对象中的name属性进行初始化
cout << "userdefined constructor" << endl;
}
MAN::person::~person()
{
// 默认析构函数是空的
}
main.cpp:
#include "person.hpp"
using namespace MAN;
int main(void)
{
// 人的一天的生活
string s1 = "linux";
person *pPerson = new person(s1); // 创建了一个person的对象,分配在堆
pPerson->name = "zhangsan";
// pPerson->p = malloc(256);
pPerson->eat();
pPerson->work();
return 0;
}
//分配在堆上
person *pPerson = new person(s1); //调用提供的构造函数
person *p = new person(); //调用默认构造函数
person *p = new person; //调用默认构造函数
//分配在栈上
person me;
person me(); //不对
3.在构造和析构函数中使用动态内存
3.1、析构函数的使用
(1)析构函数在对象对销毁时自动调用,一般有2种情况
(2)用new分配的对象,用delete显式析构
(3)分配在栈上的对象,当栈释放时自动析构
(4)普通情况下析构函数都是空的,因为不必做什么特别的事情
3.2、在class中使用动态内存变量
(1)什么情况下用动态内存?需要大块内存,且需要按需灵活的申请和释放,用栈怕爆、用全局怕浪费和死板
(2)在class person中增加一个int *指针,用于指向一个int类型元素的内存空间
(3)在构造函数中分配动态内存
(4)在析构函数中回收动态内存
(5)将动态内存从int变量升级到int数组变量
(6)实战中C++常用的动态内存往往是容器vector那些
3.3、new和delete:
this->pInt = new int(55); //给pInt分配空间并将其赋值为55
this->pInt = new int[100]; // 给pInt分配空间,为100个元素的数组
这样那么在析构函数中就要 delete[] this->pInt; 来释放内存。
3.4、用valgrind工具查看内存泄漏
(1)valgrind工具介绍:参考:https://blog.csdn.net/u012662731/article/details/78652651
(2)安装:sudo apt-get install valgrind(ubuntu16.04 X64)
(3)编译程序:主要是添加**-g**参数便于调试时有行号 g++ person.cpp main.cpp -g -o apptest
(4)使用:valgrind --tool=memcheck --leak-check=full --show-reachable=yes --trace-children=yes ./app
4.构造函数与类的成员初始化
4.1、构造函数一大功能就是初始化成员变量
(1)默认构造函数不带参,无初始化功能
(2)若无其他构造函数,则默认构造函数可以省略。但若有哪怕1个其他构造函数,则默认构造函数不能省,必须写上。
(3)栈上分配对象时,若使用默认构造函数,则对象变量后面不加空的(),若用带参构造才需要加(初始化参数)
4.2、C++的成员初始化列表
(1)一般用于带参构造函数中,用来给属性传参赋值
(2)成员初始化列表和构造函数之间用冒号间隔,多个列表项之间用逗号间隔
(3)初始化列表可以替代构造函数内的赋值语句,达到同样效果
MAN::person::person(string myname, int myage, bool mymale):name(myname),
age(myage),male(mymale)
{
this->name = name; //就等于name(myname)
}
4.3、构造函数使用参数默认值
(1)class声明时可以给函数形参赋值一个默认值,实际调用时若不传参就使用默认值:
类中的构造函数给默认值的写法:
person(string myname = "aston", int myage = 33, bool mymale = false);
(2)方法实现时形参可以不写默认值,但是实际是按照声明时的默认值规则的
(3)有默认值情况,要注意实际调用不能有重载歧义,否则编译不能通过
(4)所有参数都带默认值的构造函数,1个可以顶多个构造函数(举例说明)
5.拷贝构造函数的引入
5.1、用对象来初始化对象
MAN::person::person(const person& pn):name(pn.name),age(pn.age),male(pn.male)
{
/*
this->name = pn.name;
this->age = pn.age;
this->male = pn.male;
*/
}
(1)对象的直接初始化,是对象在分配内存之后调用了相应constructor来完成的初始化
(2)对象的用另一个对象来初始化,是对象在分配之后调用了相应的copy constructor来完成初始化
5.2、拷贝构造函数
(1)拷贝构造函数不需要重载,他的参数列表固定为const classname& xx
(2)拷贝构造函数很合适用初始化列表来实现
两种写法:
person p2(p1);
person p2 = p1;
6.浅拷贝与深拷贝
6.1、浅拷贝的缺陷
(1)上节讲的只有普通成员变量初始化的拷贝构造函数,就是浅拷贝
(2)如果不显式提供,C++会自动提供一个全部普通成员被浅拷贝的默认copy constructor
(3)浅拷贝在遇到有动态内存分配时就会出问题,举例演示:
6.2、如何解决
(1)不要用默认copy constructor(默认的都是浅拷贝),自己显式提供一个copy constructor,并且在其内部再次分配动态内存
(2)这就叫深拷贝,深的意思就是不止给指针变量本身分配内存一份,也给指针指向的空间再分配内存(如果有需要还要复制内存内的值)一份
(3)一般如果不需要深拷贝,根本就不用显式提供copy constructor,所以提供了的基本都是需要深拷贝的
(4)拷贝构造函数不需要额外的析构函数来对应,用的还是原来的析构函数
6.3、如何深度理解浅拷贝和深拷贝
(1)这个问题不是C++特有的,Java等语言也会遇到,只是语言给封起来了,而C++需要类作者自己精心处理
(2)从编程语言学角度讲,本质上是值语义value symatics和引用语义reference symatics的差别
7.什么是访问权限问题
7.1、public访问权限是全局的
(1)public的成员变量,在类的成员方法中可以直接访问
(2)public的成员变量,在任何外部代码中可以通过类的对象来直接访问
(3)public的成员方法,在类内其他成员方法中可以直接调用
(4)public的成员方法,在任何外部代码中可以通过类的对象来直接访问
(5)public就是完全不设防
7.2、private访问权限
(1)private的成员变量,在类的成员方法中可以直接访问
(2)private的成员变量,在任何外部代码中不可以通过对象来直接访问
(3)private的成员方法,在类内其他成员方法中可以直接调用
(4)private的成员方法,在任何外部代码中不可以通过对象来直接访问
(5)private就是对内不设防,对外完全设防的
7.3、更多关于访问权限问题的问题
(1)protected是第三种访问权限修饰符,如果有必要还可以有第4种甚至更多
(2)访问权限叠加类的继承、static、friend等特性后才更加显出复杂性和威力
(3)暂时只先引入这个概念,掌握基本用法和规则即可,后面逐步去深入
7.4、访问权限作用1:保护内部资源
(1)private的成员是class内部使用,外部没必要直接访问(读取或赋值),所以干脆在语法上让你看不见
(2)访问权限的保护是一种语法层面的保护,而非硬件上限制访问,硬件做不了这么细致
(3)最终目的也是为了整个程序更安全
7.5、访问权限作用2:隐藏外部无需关心的细节
(1)将class内部使用而外部绝不会用到的成员变量隐藏起来,以免干扰外部或者不小心被外部修改了
(2)隐藏细节可以降低使用类库的人的难度,调用时只看到对我有用的东西
(3)这个隐藏其实是把class的作者和使用者专业分工,是很有必要的
7.6、这就是面向对象的封装特性
(1)封装特性的体现之一就是抽象,抽象的一层意思就是隐藏掉不必要的细节
(2)封装特性的体现之一就是组织和保护,便于整个整体和外部更合理的交流
8.访问权限带来的一些新编程理念
8.1、只读或只写变量
(1)问题:你的class中需要一个变量,但是希望外部只能读不能写,怎么实现?
(2)分析:硬件、OS、编程语言都未提供这样的RO或WO的机制,只能间接实现了
(3)解决方案:利用访问权限,将成员变量设计为private,然后再通过封装成员方法来实现
(4)如果只提供read方法那就是只读成员,如果只提供write方法那就是只写成员
(5)这样的成员经常被称为属性(property),C#等高级编程语言中就源生支撑这种新编程思想
8.2、复杂程序架构化
(1)简单程序规模小,参与人少,靠人本身的逻辑能力就能保证实现和质量
(2)复杂程序规模大参与人多,协作成本高,水平良莠不齐,必须靠架构式设计才能保证实现和质量
(3)架构化设计中权限管控的思想很重要,每一层的访问权限和主要关注点都不同
8.3、为什么C语言不需要这些
(1)C主要做裸机开发和OS内核开发,都是独立一体化程序,不隔离,所以不需要也没法管控权限
(2)C程序一般规模小,不需要管控权限
(3)C程序强调性能,而权限管控一定程度会牺牲性能。凡事都有两面性。
(4)越是高级语言,编程越偏业务逻辑,就越需要权限管控和架构思想这些。
#ifndef __PERSON_H__
#define __PERSON_H__
#include <string>
using namespace std;
namespace MAN
{
// 声明这个类
class person
{
int a;
public:
// 属性
string name; // 名字
int age; // 年龄
// 提供一个专门用来写age成员变量的方法
void agewrite(int a);
int ageread();
private:
int age; // 我们希望age是只写不能读的
protected:
};
} // end of namespace MAN
#endif
9.struct和class的区别
9.1、C和C++中struct的区别
(1)C中不支持成员函数(只能通过函数指针成员变量间接支持),而C++源生支持。
(2)C中不支持static成员,而C++中支持。C++ static class 是一个大知识点
9.2、C++中struct和class的区别
(1)默认成员权限,struct默认public,class默认private
(2)继承关系的权限管控,struct默认public,class默认private
(3)struct和class交叉继承时,默认的权限管控取决于子类而不是基类
10.const和mutable
10.1、const可以实现常函数
(1)常函数,就是class的成员函数承诺在函数内部不会修改class的任何成员变量,注意是任何一个
(2)常函数演示案例
#include <string>
using namespace std;
namespace MAN
{
class person
{
public:
mutable string name;
bool male;
person(){};
person(int myage);
void AgeAdd1(void);
int AgeGet(void) const;
private:
mutable int age; //mutable为const打洞
};
} // end of namespace MAN
void MAN::person::AgeAdd1(void)
{
this->age++;
}
// 本函数只是读取class的成员变量,并不修改,所以可以是常函数
int MAN::person::AgeGet(void) const
{
age++;
name = "ddd";
return this->age;
}
在类中给出两个方法AgeAdd1、AgeGet其中AgeGet是常函数,在main.cpp中想通过AgeGet输出类的信息,通常不是直接在main函数中通过cout打印,而是封装一个函数。对于类的使用者,使用类的时候会封装一些函数来完成功能,函数传参的时候会用引用。回顾c程序,对于一个在函数中不会修改的指针我们会加const来修饰,c++也一样,但是c++中函数中如果有用到成员方法,那么此方法必须是常函数,也就是在类中,此函数必须加const修饰。
(3)思考:C++为什么设计常函数?还是为了class的设计者和使用者更好的协作,避免错误的使用类库
10.2、mutable可以局部打破const常函数
(1)const常函数承诺在函数内不会修改class的任何一个成员变量
(2)但是有时候个别成员变量,就是需要在const的常函数中也能修改(注意只是个别,其他大部分是不需要修改的)
(3)怎么办?2个解法:要么去掉const,要么使用mutable局部打洞
(4)mutable使用演示
(5)思考:C++为什么设计mutable?和private那里一样,还是“先全部禁了再按需打开”的思路。
11.扫尾和总结
11.1、class的前置声明
(1)就是class的声明,安慰编译器的
(2)看到了认识即可
11.2、inline member function
(1)类的声明中直接写函数体,则此函数会被编译器inline化处理
(2)类的声明中正常处理,而成员函数的实现中加inline
(3)inline的成员函数应该放在hpp中而不是cpp中,这个一定要注意,因为inline是在编译时替换的