【C++系列(合集)】特性多又复杂?不存在!——这篇C++大全直接干碎(超级大全,精讲)(三)

简介: 【C++系列(合集)】特性多又复杂?不存在!——这篇C++大全直接干碎(超级大全,精讲)

九.C/C++的内存管理

1.C/C++的内存分布规则

栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。

内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口 创建共享共享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)

堆用于程序运行时动态内存分配,堆是可以上增长的。

数据段--存储全局数据和静态数据。

代码段--可执行的代码/只读常量。

图示:  

image.png一.易错点:数组存储字符串和指针指向字符串,解引用后所在的位置不同(含例题)

*char2数组所在的位置是栈,是位于代码段(常量区)"abcd\0"的一份拷贝;

pChar3是一个指向代码段(常量区)"abcd\0"的一个指针变量,由于其具有常性,所以要加上const;

图示:

image.png

2.C/C++的内存管理方式

PS:C的内存管理有malloc/calloc/realloc/free(可见博主C专栏:动态内存管理)  


引入:C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。(一般C与C++内存管理不混用)


一.使用new和delete操作符的使用规范

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用 new[]和delete[],要匹配起来使用。(如果new后接free,无论是否是对同一块空间的操作,都容易报错)


代码演示:

//报错
int* p3 = (int*)malloc(sizeof(int)); // C
int* p4 = new int;
free(p4);           对开辟同一块空间操作,不匹配
delete p3;
//报错
int* p3 = (int*)malloc(sizeof(int)); // C
int* p4 = new int;
free(p3);           对开辟不同一块空间操作,不匹配
delete p4;

二.new和delete对内置类型的具体使用场景

使用场景:

  • 申请一个int类型空间(调用构造,随机值)
  • 申请一个int类型空间,并初始化为10
  • 申请10个int类型空间
  • 申请10个int类型空间,并分别初始化

代码演示:

void Test()
{
  // 动态申请一个int类型的空间
  int* ptr4 = new int;
  // 动态申请一个int类型的空间并初始化为10
  int* ptr5 = new int(10);
  // 动态申请10个int类型的空间
  int* ptr6 = new int[10];
  // 动态申请10个int类型的空间,并初始化
  int* ptr7 = new int[10]{1,3,4};
  delete ptr4;
  delete ptr5;
  delete[] ptr6;
}

三.new和delete对自定义类型的具体使用场景

使用场景:有一个自定义类型A,他的初始化列表需要传入两个参数

  • 申请一个空间给A
  • 申请一个4个空间给4个A,分别初始化(多参,不可不完全初始化)

代码演示:

void test()
{
  A* p1 = new A(1,1);
  delete p2;
  //错误写法:不完全初始化  A* p2 = new A[4]{ A(1,1),A(2,2),A(3,3)};
  A* p2 = new A[4]{ A(1,1),A(2,2),A(3,3),A(4,4) };
  delete[] p2;
}

四.new/delete与malloc/free的底层区别(自定义类型演示)

new/delete 和 malloc/free根本区别:


new的底层其实也是malloc,与malloc不同之处在于他会调用拷贝构造

delete的底层其实也是free,与free不同之处在于他会调用析构函数

实例分析:(顺序)


在下图中,new了一个栈Stack,其实底层是先malloc个空间给Stack(自定义类型),再调用它的拷贝构造(_array指向的新空间);

当delete栈Stack时,先调用析构函数(free掉刚刚_array指向的新空间),再free掉Stack所处的空间;

PS:如果不是这样,而是先free掉Stack的空间,那么_array指向的新空间将无法被p1找到,造成内存泄漏

图演示:  

image.png

五. new/delete与malloc/free在使用失败时的区别

  • C++是一门面向对象的语言,处理失败时,不喜欢用返回值,更喜欢用抛异常
  • 一般用【try-catch捕捉

代码演示:

    try
  {
  do
  {
    //p1 = (int*)malloc(1024 * 1024);
    p1 = new int[1024 * 1024];
    cout << p1 << endl;
  } while (p1);
  }
catch (const exception& e)
{
  cout << e.what() << endl;
}

二.模板


1.函数模板


一.函数模板概念

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,据实参类型产生函数的特定类型版本


二.函数模板的格式

​template<typename T>
void Swap( T& left, T& right)
{
 T temp = left;
 left = right;
 right = temp;
}
//可识别不同的同种类型交换(例:char与char,int与int,double与double)

PS:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)


三.函数模板的实例化

引入:用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。实例化实现的任务是交给编译器的。


1.隐式实例化

引入:隐式实例化的机制是让编译器根据实参推演模板参数的实际类型,而这往往会出现一些问题


适用情况:其交换的两者是同一类

image.png

不适用情况:其交换的两者不是同一类

template<class T>
T Add(const T& left, const T& right)
{
 return left + right;
}
int main()
{
 int a1 = 10;
 double d1 = 10.0;
 Add(a1, d1);
//解决方式:Add(a1, (int)d1);强制类型转换
}

分析:


该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型 通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T, 编译器无法确定此处到底该将T确定为int 或者 double类型而报错


解决方式:


用户自己强制类型转换

显式实例化

2.显式实例化

显式实例化:在函数名后的<>中指定模板参数的实际类型


代码演示:

int main(void)
{
 int a = 10;
 double b = 20.0;
 // 显式实例化
 Add<int>(a, b);
 return 0;
}

3.模板参数的匹配原则

一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数

对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模 板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板

模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

2.类模板

一.类模板的格式

template<class T1, class T2, ..., class Tn>
class 类模板名
{
 // 类内成员定义
}; 

二.类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

1. Vector<int> s1;
2. Vector<double> s2;

注意区分:

  • 类名等同于类型
  • 类模板类型是类型,类名是类名

例如:在下面代码中,类模板中函数放在类外进行定义时,需要加模板参数列表;在访问类模板时,要用Vector<T>类型),而不是Vector(类名)

代码演示:

template<class T>
class Vector
{ 
public :
 Vector(size_t capacity = 10)
 : _pData(new T[capacity])
 , _size(0)
 , _capacity(capacity)
 {}
 // 使用析构函数演示:在类中声明,在类外定义。
 ~Vector();
 void PushBack(const T& data);
 void PopBack();
 // ...
 size_t Size() {return _size;}
 T& operator[](size_t pos)
{
 assert(pos < _size);
 return _pData[pos];
 }
private:
 T* _pData;
 size_t _size;
 size_t _capacity;
};
// 注意:类模板中函数放在类外进行定义时,需要加模板参数列表
template <class T>
Vector<T>::~Vector()//用类型访问类模板
{
 if(_pData)
 delete[] _pData;
 _size = _capacity = 0;
}

三.string类

十.面向对象(含与过程对比)

面向对象和面向过程的对比:


面向对象更注重对象与对象之间的关系和交互——现实世界类和对象映射到虚拟计算机系统。例:商家,骑手,用户之间的关系


面向过程更多指的是实现目的过程步骤:上架->点餐->派单->送餐


通俗而言:即对象与事的区别


十一.面向对象的三大特征 (含类的概念)

面向对象的三大特性:封装,继承,多态


1.封装

1.访问限定符(C++实现封装的方式)

image.png

image.png

2. 在C++语言中实现封装

 封装本质上属于一种管理。例:计算机设计中的开机键,usb插口等等,让用户通过这些和计算机交互。而实际工作的是硬件元件。


 在C++中实现封装,可以通过类和操作数据的方法进行结合,通过访问权限(访问限定符)来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。


例:在设计通讯录的项目中,往往会建立结构体Steplist,以及各种增删查改的函数。但是使用者可以同时通过函数和通过修改结构体来实现功能,就会造成使用上的差异性(比如需要区分某个变量top表示的是末元素还是末元素的下一个区域)。C语言阶段通讯录写法:

image.png

C++运用类以后的写法:

image.png

相关文章
|
1月前
|
编译器 程序员 定位技术
C++ 20新特性之Concepts
在C++ 20之前,我们在编写泛型代码时,模板参数的约束往往通过复杂的SFINAE(Substitution Failure Is Not An Error)策略或繁琐的Traits类来实现。这不仅难以阅读,也非常容易出错,导致很多程序员在提及泛型编程时,总是心有余悸、脊背发凉。 在没有引入Concepts之前,我们只能依靠经验和技巧来解读编译器给出的错误信息,很容易陷入“类型迷路”。这就好比在没有GPS导航的年代,我们依靠复杂的地图和模糊的方向指示去一个陌生的地点,很容易迷路。而Concepts的引入,就像是给C++的模板系统安装了一个GPS导航仪
108 59
|
1月前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(三)
【C++】面向对象编程的三大特性:深入解析多态机制
|
1月前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(二)
【C++】面向对象编程的三大特性:深入解析多态机制
|
1月前
|
编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(一)
【C++】面向对象编程的三大特性:深入解析多态机制
|
1月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
1月前
|
C++
C++ 20新特性之结构化绑定
在C++ 20出现之前,当我们需要访问一个结构体或类的多个成员时,通常使用.或->操作符。对于复杂的数据结构,这种访问方式往往会显得冗长,也难以理解。C++ 20中引入的结构化绑定允许我们直接从一个聚合类型(比如:tuple、struct、class等)中提取出多个成员,并为它们分别命名。这一特性大大简化了对复杂数据结构的访问方式,使代码更加清晰、易读。
34 0
|
2月前
|
编译器 C++ 计算机视觉
C++ 11新特性之完美转发
C++ 11新特性之完美转发
50 4
|
2月前
|
Java C# C++
C++ 11新特性之语法甜点1
C++ 11新特性之语法甜点1
33 4
|
2月前
|
安全 程序员 编译器
C++ 11新特性之auto和decltype
C++ 11新特性之auto和decltype
39 3
|
2月前
|
设计模式 缓存 安全
C++ 11新特性之week_ptr
C++ 11新特性之week_ptr
36 2