C++知识点整理

1、const(729)

  • const 定义的常量在超出其作用域之后其空间会被释放,而 static 定义的静态常量在函数执行后不会释放其存储空间。
  • 1、const 修饰基本类型数据类型:基本数据类型,修饰符 const 可以⽤在类型说明符前,也可以⽤在类型说明符后,其结果是⼀样的。在使⽤这些常量的时候,只要不改变这些常量的值即可
  • 如果想在多个文件之间共享const对象,必须在变量的定义前添加extern关键字,在头文件中声明也加上extern关键字
  • 2、const 修饰指针变量和引⽤变量:如果 const 位于⼩星星的左侧(const int *a),则 const 就是⽤来修饰指针所指向的变量,即指针指向为常量;如果 const 位于⼩星星的右侧( int* const b),则 const 就是修饰指针本身,即指针本身是常量
  • 3、const 成员函数: const 成员函数的主要⽬的是防⽌成员函数修改对象的内容。
  • 要注意, const 关键字和 static 关键字对于成员函数来说是不能同时使⽤的,因为 static 关键字修饰静态成员函数不含有 this 指针,即不能实例化const 成员函数⼜必须具体到某⼀个函数
  • 4、const 修饰类对象,定义常量对象:**常量对象只能调⽤常量函数,**别的成员函数都不能调⽤
  • const 成员函数中如果实在想修改某个变量,可以使⽤ mutable 进⾏修饰
  • 5、常量对象、常量对象的引用、指针都只能调用常量成员函数

1.1 static 与 const

在这里插入图片描述

  • static关键字的作用
  • -代码块内部的变量声明,加上static作用域和链接属性不变。变量在程序执行之前就创建,在程序执行的整个周期都存在
  • 对于被static修饰的普通函数,其只能在定义它的源文件中使用,不能在其他源文件中被引用
  • 对于被static修饰的类成员变量和成员函数,它们是属于类的,而不是某个对象,所有对象共享一个静态成员。**静态成员通过<类名>::<静态成员>**来使用。
  • 注意:静态⾮常量数据成员,其只能在类外定义和初始化,在类内仅是声明⽽已
    在这里插入图片描述

1.2 define和const区别

1、类型和安全检查不同

  • define宏定义是字符替换,没有数据类型的区别,同时这种替换没有类型安全检查,可能产生边际效应等错误;
  • const常量是常量的声明,有类型区别,需要在编译阶段进行类型检查

2、编译器处理不同

  • 宏定义是一个"编译时"概念,在预处理阶段展开,不能对宏定义进行调试,生命周期结束与编译时期;
  • const常量是一个"运行时"概念,在程序运行使用,类似于一个只读行数据

3、存储方式不同

  • 宏定义是直接替换,不会分配内存,存储代码段中
  • const常量需要进行内存分配,存储数据段中

4、定义域不同

cvoid f1 ()
{ 
	#define N 12
	const int n 12;
}
void f2 ()
{
	cout<<N <<endl; //正确,N已经定义过,不受定义域限制
	cout<<n <<endl; //错误,n定义域只在f1函数中
}

5、定义后能否取消

  • 宏定义可以通过#undef来使之前的宏定义失效
    const常量定义后将在定义域内永久有效

6、是否可以做函数参数

2、宏定义的优缺点(百度、bigo)

优点

  • 1、提高了程序的可读性,同时也方便进行修改
  • 2、提高程序的运行效率使用带参的宏定义既可完成函数调用的功能,又能避免函数的出栈与入栈操作,减少系统开销,提高运行效率;
  • 3、宏是由预处理器处理的,通过字符串操作可以完成很多编译器无法实现的功能。比如##连接符

缺点

  • 1、由于是直接嵌入的,所以代码可能相对多一点;
  • 2、嵌套定义过多可能会影响程序的可读性,而且很容易出错
  • 3、对带参的宏而言,由于是直接替换,并不会检查参数是否合法,存在安全隐患
  • 预编译语句仅仅是简单的值代替,缺乏类型的检测机制。这样预处理语句就不能享受C++严格的类型检查的好处,从而可能成为引发一系列错误的隐患。

3、说一下C++和C的区别

设计思想上:

  • C++是面向对象的语言,而C是面向过程的结构化编程语言

语法上:

  • C++具有封装、继承和多态三种特性
  • C++相比C,增加多许多类型安全的功能,比如强制类型转换、
  • C++支持范式编程,比如模板类、函数模板等

4、C++中四种cast转换

C++中四种类型转换是:static_cast, dynamic_cast, const_cast, reinterpret_cast

  • const_cast:用于将const变量转为非const
  • static_cast:用于各种隐式转换,比如非const转const,void*转指针等, static_cast能用于多态向上转化,如果向下转能成功但是不安全,结果未知;
  • dynamic_cast:用于动态类型转换只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。
    向上转换:指的是子类向基类的转换
    向下转换:指的是基类向子类的转换
    向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。
  • reinterpret_cast:几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用;
  • 为什么不使用C的强制转换?:C的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错

5、C/C++ 中指针和引用的区别

  • 指针有自己的一块空间,而引用只是一个别名
  • 使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小;
  • 指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象 的引用;
  • 可以有const指针,但是没有const引用
  • 指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能被改变
  • 指针可以有多级指针(**p),而引用止于一级
  • 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。

6、野指针是什么?什么时候会发生段错误?

  • 野指针就是指向一个已删除的对象或者未申请访问受限内存区域的指针
  • 段错误通常发生在访问非法内存地址的时候。1、使用野指针。2、试图修改字符串常量的内容

7、C++中的智能指针(百度,性能开销)

  • C++里面的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11支持,并且第一个已经被11弃用。
  • 智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间
  • unique_ptr(替换auto_ptr):unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于**避免资源泄露(**例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用
  • unique_ptr pu1(new string (“hello world”));
  • shared_ptr:实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。它使用计数机制来表明资源被几个指针共享。
    通过成员函数use_count()来查看资源的所有者个数。
    调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。
    swap 交换两个 shared_ptr 对象(即交换所拥有的对象)
    reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少
    get 返回内部对象(指针), 由于已经重载了()方法, **因此和直接使用对象是一样的.**如 shared_ptr sp(new int(1)); sp 与 sp.get()是等价的
  • weak_ptr:weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象。进行该对象的内存管理的是那个强引用的 shared_ptr。 weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr是用来解决shared_ptr相互引用时的死锁问题。
    weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。
    它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。
    注意的是不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问,pa->pb_->print(); 英文pb_是一个weak_ptr,应该先把它转化为shared_ptr,如:shared_ptr p = pa->pb_.lock(); p->print();

8、多态析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数?析构函数作用?

  • 将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。
  • C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此C++默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数
  • 只能有一个析构函数,不能重载。如果一个类中有指针,且在使用的过程中动态的申请了内存,那么最好显示构造析构函数在销毁类之前,释放掉申请的内存空间,避免内存泄漏。
  • 类析构顺序:1)派生类本身的析构函数;2)对象成员析构函数;3)基类析构函数。

9、函数指针(802)

  • 函数指针是一个指向函数的指针变量,C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是大体一致的。
  • 用途:调用函数和做函数的参数,比如回调函数。

10、fork函数

  • 创建一个和当前进程映像一样的进程可以通过fork( )系统调用
  • 最常见的fork( )用法是创建一个新的进程,然后使用exec( )载入二进制映像,替换当前进程的映像。
  • 现代的Unix系统采取了更多的优化如 Linux,采用了写时复制的方法,而不是对父进程空间进程整体复制。

11、map和set区别,底层实现?

  • map中的元素是key-value(关键字—值)对,Set与之相对就是关键字的简单集合,set中每个元素只包含一个关键字。
  • set的迭代器是const的,不允许修改元素的值;map允许修改value,但不允许修改key。
  • map支持下标操作,set不支持下标操作。
  • map和set都是C++的关联容器,其底层实现都是红黑树(RB-Tree)。由于 map 和set所开放的各种操作接口,RB-tree 也都提供了,所以几乎所有的 map 和set的操作行为,都只是转调 RB-tree 的操作行为

12、介绍一下STL的allocator

  • STL的分配器用于封装STL容器在内存管理上的底层细节
  • C++中,其内存配置和释放如下:
  • new运算分两个阶段:(1)调用::operator new配置内存;(2)调用对象构造函数构造对象内容
  • delete运算分两个阶段:(1)调用对象析构函数;(2)调用::operator delete释放内存
  • 为了精密分工,STL allocator将两个阶段操作区分开来:
  • (1)内存配置由alloc::allocate()负责,(2)对象构造由::construct()负责
  • (1)对象析构由::destroy()负责,(2)内存释放由alloc::deallocate()负责
  • 同时为了提升内存管理的效率,减少申请小内存造成的内存碎片问题,SGI STL采用了两级配置器,当分配的空间大小超过128B时,会使用第一级空间配置器;当分配的空间大小小于128B时,将使用第二级空间配置器。
  • 第一级空间配置器直接使用malloc()、realloc()、free()函数进行内存空间的分配和释放,而第二级空间配置器采用了内存池技术,通过空闲链表来管理内存。

13、C++中类成员的访问权限

成员访问限定符

  • public公有的
  • protected受保护的:希望与派生类分享但不想被其他公共访问。
  • private私有的
  • 类的内部(定义类的代码内部),无论成员被声明为 public、protected 还是 private,都是可以互相访问的,没有访问权限的限制
  • 类的外部(定义类的代码之外),只能通过对象访问成员,并且通过对象只能访问 public 属性的成员,不能访问 private、protected 属性的成员

14、C++中struct和class的区别

  • 在C++中,可以用struct和class定义类,都可以继承。
  • 区别在于:structl的默认继承权限和默认访问权限是public,而class的默认继承权限和默认访问权限是private。

15、C++源文件从文本到可执行文件经历的过程(百度,链接怎么做的)

  • 对于C++源文件,从文本到可执行文件一般需要四个过程
  • 预处理阶段:对源代码文件中文件包含关系(头文件)、预编译语句(宏定义)进行分析和替换,生成预编译文件
  • 编译阶段:将经过预处理后的预编译文件转换成特定汇编代码生成汇编文件
  • 汇编阶段:将编译阶段生成的汇编文件转化成机器码,生成可重定位目标文件
  • 链接阶段:将多个目标文件及所需要的库连接成最终的可执行目标文件

16、include头文件双引号””和尖括号<>的区别

Include头文件的顺序:对于include的头文件来说,如果在文件a.h中声明一个在文件b.h中定义的变量,而不引用b.h。那么要在a.c文件中引用b.h文件,并且要先引用b.h,后引用a.h,否则汇报变量类型未声明错误。

  • 双引号和尖括号的区别:编译器预处理阶段查找头文件的路径不一样
  • 对于使用双引号包含的头文件,查找头文件路径的顺序为:
    1、当前头文件目录(尖括号的没有这一步)
    2、编译器设置的头文件路径(编译器可使用-I显式指定搜索路径)
    3、系统变量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的头文件路径

17、C++的内存管理是怎样的?

  • 在C++中,虚拟内存分为代码段、数据段、BSS段、堆区、内存映射区以及栈区六部分
    在这里插入图片描述
  • 1、代码段:包括只读存储区和文本区,其中只读存储区存储字符串常量文本区存储程序机器代码
  • 2、数据段:存储程序中已初始化全局变量和静态变量
  • 3、bss 段:存储未初始化全局变量和静态变量(局部+全局),以及所有被初始化为0的全局变量和静态变量。【数据段(2,3)包括初始化的数据和未初始化的数据(BSS)两部分,静态分配】
  • --------------------上面是静态区域,下面是动态区域--------------------
  • 4、堆区:调用new/malloc函数时在堆区动态分配内存,同时需要调用delete/free来手动释放申请的内存。
  • 5、映射区:存储动态链接库以及调用mmap函数进行的文件映射
  • 6、:使用栈空间存储函数的返回地址、参数、局部变量、返回值

18、C++的内存分配?

  • 32bitCPU可寻址4G线性空间,每个进程都有各自独立的4G逻辑地址,其中0-3G是用户态空间,3~4G是内核空间,不同进程相同的逻辑地址会映射到不同的物理地址中。

  • 动态区域:

  • 1、heap(堆): 当进程未调用malloc时是没有堆段的,只有调用malloc时采用分配一个堆,并且在程序运行过程中可以动态增加堆大小(移动break指针),从低地址向高地址增长。分配小内存时使用该区域。 堆的起始地址由mm_struct 结构体中的start_brk标识,结束地址由brk标识。

  • 2、memory mapping segment(映射区):存储动态链接库等文件映射、申请大内存(malloc时调用mmap函数)

  • 3、stack(栈):使用栈空间存储函数的返回地址、参数、局部变量、返回值,从高地址向低地址增长。在创建进程时会有一个最大栈大小,Linux可以通过ulimit命令指定。

19、malloc的原理,另外brk系统调用和mmap系统调用的作用分别是什么?(内存池讲的不好,稍后更新)

  • Malloc函数用于动态分配内存。malloc在申请内存时,一般会通过brk或者mmap系统调用进行申请。
  • malloc申请内存小于128K时,会使用系统函数brk在堆区中分配
  • malloc申请内存大于128K时,会使用系统函数mmap在映射区分配
  • 原理:malloc其采用内存池的方式,减少内存碎片和系统调用的开销。
  • 1、先申请大块内存作为堆区,然后将堆区分为多个内存块,以块作为内存管理的基本单位
  • 2、当用户申请内存时,直接从堆区分配一块合适的空闲块。
  • 3、Malloc采用隐式链表结构将堆区分成连续的、大小不一的块,包含已分配块和未分配块
  • 4、同时malloc采用显示链表结构来管理所有的空闲块,即使用一个双向链表将空闲块连接起来,每一个空闲块记录了一个连续的、未分配的地址。
  • 5、当进行内存分配时,malloc会通过隐式链表遍历所有的空闲块,选择满足要求的块进行分配;
  • 6、当进行内存合并时,malloc采用边界标记法,根据每个块的前后块是否已经分配来决定是否进行块合并。

20、内存泄漏?分类?如何判断?什么原因?怎么解决?(百度)

  • 内存泄漏(memory leak)是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费
  • 分类
  • 1、堆内存泄漏 (Heap leak)。根据需要分配通过malloc,realloc new等从堆中分配的一块内存,再是完成后必须通过调用对应的 free或者delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak.
  • 2、系统资源泄露(Resource Leak)。主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。
  • 3、没有将基类的析构函数定义为虚函数。当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确是释放,因此造成内存泄露
  • 判断
  • 1、可以使用linux环境下的内存泄漏检查工具Valgrind
  • 2、另一方面我们在写代码时可以添加内存申请和释放的统计功能,统计当前申请和释放的内存是否一致,以此来判断内存是否泄露。
  • 原因
  • 1、通常是由于调用了malloc/new等内存申请的操作,但是缺少了对应的free/delete
  • 解决
  • 1、使用智能指针
  • 2、添加内存申请和释放的统计功能

21、new和malloc的区别(百度,整理中)

  • 1、new分配内存按照数据类型进行分配,malloc分配内存按照指定的大小分配;

  • 2、new返回的是指定对象的指针,而malloc返回的是void*,因此malloc的返回值一般都需要进行类型转化。

  • 3、new不仅分配一段内存,而且会调用构造函数,malloc不会。

  • 4、new分配的内存要用delete销毁,malloc要用free来销毁;**delete销毁的时候会调用对象的析构函数,**而free则不会。

  • 5、new是一个操作符可以重载,malloc是一个库函数。

  • 6、malloc分配的内存不够的时候,可以用realloc扩容。new没用这样操作。

  • 7、new如果分配失败了会抛出bad_malloc的异常,而malloc失败了会返回NULL。

  • 8、申请数组时: new[]一次分配所有内存,多次调用构造函数,搭配使用delete[],delete[]多次调用析构函数,销毁数组中的每个对象。而malloc则只能sizeof(int) * n。

22、右值引用(百度)

  • 在 C++ 中在进行对象赋值操作的时候,很多情况下会发生对象之间的深拷贝,如果堆内存很大,这个拷贝的代价也就非常大,在某些情况下,如果想要避免对象的深拷贝,就可以使用右值引用进行性能的优化
  • 右值引用具有移动语义移动语义可以将资源(堆、系统对象等)通过浅拷贝从一个对象转移到另一个对象这样就能减少不必要的临时对象的创建、拷贝以及销毁,可以大幅提高 C++ 应用程序的性能。
   // 添加移动构造函数
   Test(Test&& a) : m_num(a.m_num)
   {
       a.m_num = nullptr; //int* m_num;
       cout << "move construct: my name is sunny" << endl;
   }
  • 进行Test t = getObj();操作的时候并没有调用拷贝构造函数进行深拷贝,而是调用了移动构造函数,在这个函数中只是进行了浅拷贝,没有对临时对象进行深拷贝,提高了性能。在进行赋值操作的时候如果 = 右边是一个右值,那么移动构造函数就会被调用。
  • 移动构造中使用了右值引用,会将临时对象中的堆内存地址的所有权转移给对象t,这块内存被成功续命,因此在t对象中还可以继续使用这块内存。
  • 对于需要动态申请大量资源的类,应该设计移动构造函数,以提高程序效率。需要注意的是,我们一般在提供移动构造函数的同时,也会提供常量左值引用的拷贝构造函数,以保证移动不成还可以使用拷贝构造函数。
  • 模板参数需要指定为 T&&,如果是自动类型推导需要指定为 auto &&,在这两种场景下 && 被称作未定的引用类型。当它作为参数时,有可能被一个右值引用初始化,也有可能被一个左值引用初始化,通过右值推导 T&& 或者 auto&& 得到的是一个右值引用类型。通过非右值(右值引用、左值、左值引用、常量右值引用、常量左值引用)推导 T&& 或者 auto&& 得到的是一个左值引用类型
  • const T&& 表示一个右值引用,不是未定引用类型。
  • 编译器会将已命名的右值引用视为左值,将未命名的右值引用视为右值。

24、转移move(百度,注意底层实现)

  • 使用std::move方法可以将左值转换为右值。使用这个函数并不能移动任何东西,而是和移动构造函数一样都具有移动语义,将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝。
  • 如果一个对象内部有较大的堆内存或者动态数组时,使用 move () 就可以非常方便的进行数据所有权的转移。使用 move 几乎没有任何代价,只是转换了资源的所有权。
  • 从实现上讲,std::move 基本等同于一个类型转换:static_cast<T&&>(lvalue);,函数原型如下:
template<class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) _NOEXCEPT
{	// forward _Arg as movable
    return (static_cast<remove_reference_t<_Ty>&&>(_Arg));
}

25、完美转发forward(百度)

  • 右值引用类型是独立于值的,一个右值引用作为函数参数的形参时,在函数内部转发该参数给内部其他函数时,它就变成一个左值,并不是原来的类型了。如果需要按照参数原来的类型转发到另一个函数,可以使用 C++11 提供的 std::forward () 函数,该函数实现的功能称之为完美转发
    -当T为左值引用类型时,t将被转换为T类型的左值,当T不是左值引用类型时,t将被转换为T类型的右值。

26、虚函数/纯虚函数/多态/继承(805)

  • C++多态分为静态多态和动态多态。静态多态是通过重载和模板技术实现,在编译的时候确定动态多态通过虚函数和继承关系来实现,执行动态绑定,在运行的时候确定
  • 动态多态实现有几个条件:(1) 虚函数;(2) 一个基类的指针或引用指向派生类的对象;
  • 使用基类的引用或者指针调用一个虚函数时将发生动态绑定。
  • 虚函数表直接从基类也继承过来,如果覆盖了其中的某个虚函数,那么虚函数表的指针就会被替换
  • 每个对象中保存的只是一个虚函数表的指针
  • 虚函数表指针指向只读数据段中的类虚函数表,虚函数表中存放着一个个函数指针,函数指针指向代码段中的具体函数。
    在这里插入图片描述

27、静态变量什么时候初始化

  • 静态变量存储在虚拟地址空间的数据段和bss段
  • C语言中其在代码执行之前初始化,属于编译期初始化
  • C++中由于引入对象,对象生成必须调用构造函数,因此C++规定全局或局部静态对象当且仅当对象首次用到时进行构造

28、volatile 关键字

  • 遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。
  • 多线程下的volatile:当两个线程都要用到某一个变量且该变量的值会被改变时,应该用 volatile 声明,该关键字的作用是防止优化编译器把变量从内存装入 CPU 寄存器中。volatile 的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值

29、函数返回值/引用

  • 1、返回一个值的方式和初始化一个变量的方式,和传形参一样,返回的值用于初始化调用点的一个临时变量。
  • 2、不要返回局部对象的引用或指针,不然会引发未定义行为。
  • 3、返回局部对象的引用和局部对象的指针都是错误的。
  • **4、引用返回左值,其他返回类型得到右值。**如果返回类型是常量引用,不能给调用的结果赋值。

30、继承与组合

  • 1、

31、构造函数

  • 1、不写构造函数就会默认合成一个构造函数
  • 2、如果成员是const或者引用的话,必须将其初始化(通过构造函数初始值列表为这些成员提供初值)

2、编写类String的构造函数、析构函数和赋值函数

  • 当类中包括指针类成员变量时一定要重载其拷贝构造函数、赋值函数和析构函数,深拷贝
    string.h
#ifndef __MYSTRING__
#define __MYSTRING__
#include <iostream>
#include <cstring>
using namespace std;
//在这个类中包括了指针类成员变量m_data,当类中包括指针类成员变量时,一定要重载其拷贝构造函数、赋值函数和析构函数
class String
{
public:                                 
   String(const char* cstr = nullptr);//普通构造函数                  
   String(const String& str); // 拷贝构造函数                    
   String& operator=(const String& str); // 拷贝赋值函数        
   ~String();                                    
   char* get_c_str() const { return m_data; }//用于输出
private:
   char* m_data; // 用于保存字符串 
};


inline String::String(const char* cstr)
{
   if (cstr) //判断空字符串
   {
      m_data = new char[strlen(cstr)+1];
      strcpy(m_data, cstr);
   }
   else //空字符串自动申请存放结束标志'\0'的空
   {   
      m_data = new char[1];
      *m_data = '\0';
   }
}

inline String::~String()
{
   delete[] m_data;
}

inline String::String(const String& str)
{
   m_data = new char[ strlen(str.m_data) + 1 ];
   strcpy(m_data, str.m_data);
}

inline String& String::operator=(const String& str)//输入参数为const型
{
   if (this == &str)//检查自赋值
      return *this;

   delete[] m_data;//释放原有的内存资源
   m_data = new char[ strlen(str.m_data) + 1 ];//创建新的内存
   strcpy(m_data, str.m_data); //拷贝
   return *this;
}

ostream& operator<<(ostream& os, const String& str)
{
   os << str.get_c_str();
   return os;
}

#endif

测试test.cpp

#include "string.h"
#include <iostream>

using namespace std;

int main()
{
  String s1("hello"); 
  cout << "s1: " << s1 << endl; 

  String s2("world");
  String s3(s2);
  cout << "s3: " << s3 << endl;  

  String str = "helloworld";
  String s4(str);
  cout << "s4: " << s4 << endl;  

  s3 = s1;
  cout << "s3: " << s3 << endl;      
     
}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值