C++笔记

C++源文件编译过程

  1. 预处理阶段:处理#include编译指令,完成包含文件在复制插入,处理预编译语句(宏定义),完成文本替换,处理条件编译指令,生成预编译文件
  2. 编译阶段:将预编译文件翻译为汇编文件
  3. 汇编阶段:将汇编文件转化为二进制代码,生成可重定位目标文件
  4. 链接阶段:将目标文件及库链接生成可执行文件

C++头文件包含顺序

  1. xxx.h
  2. C 系统文件
  3. C++ 系统文件
  4. 其他库的 .h 文件
  5. 本项目内 .h 文件

#include< >和#include“ ”的区别

一种是在包含指令#include后面”<>”将头文件名括起来。这种方式用于标准或系统提供的头文件,到保存系统标准头文件的位置查找头文件。

另一种是在包含指令#include后用双引号” ”将头文件包括起来。这种方式常用与程序员自己的头文件。用这种格式时,C编译器先查找当前目录是否有指定名称的头文件,然后在从标准头文件目录中查找。

转义字符

进制转换

  1. 十进制整数转二进制

十进制整数转换成二进制采用“2倒取余”,十进制小数转换成二进制小数采用“2取整顺序排列

位运算以及用途详解

内存储存数据的基本单位是字节(Byte),一个字节由8个位(bit)所组成。位是用以描述电脑数据量的最小单位。

x&(x-1)统计x中1的个数,x|(x+1)统计x中0的个数

strlen函数与sizeof的区别

strlen

size_t strlen(char const* str);

strlen 是一个函数,它用来计算指定字符串 str 的长度,但不包括结束字符(即 null 字符)

函数 strlen 的返回结果是 size_t 类型(即无符号整型),而 size_t 类型绝不可能是负的

sizeof

sizeof是单目运算符,它的参数可以是数组、指针、类型、对象、函数等

sizeof 在编译时计算缓冲区的长度

Malloc

extern void *malloc(unsigned int num_bytes);

 如果分配成功:则返回指向被分配内存空间的指针

       不然,返回空指针NULL。

       同时,当内存不再使用的时候,应使用free()函数将内存块释放掉。

       关于:void *,表示未确定类型的指针。C,C++规定,void *类型可以强转为任何其他类型的指针。

malloc 函数其实就是在内存中:找一片指定大小的空间,然后将这个空间的首地址给一个指针变量,这里的指针变量可以是一个单独的指针,也可以是一个数组的首地址, 这要看malloc函数中参数size的具体内容。我们这里malloc分配的内存空间在逻辑上是连续的,而在物理上可以不连续。我们作为程序员,关注的 是逻辑上的连续,其它的,操作系统会帮着我们处理的。

Malloc、free为标准库函数,new、delete为运算符,支持重载。

cin.get()

//wait until user hit the keyboard

cin.getline(char,n)

getline(cin,string)

引用

引用一般的概念称为变量的别名,定义的时候必须初始化绑定一个指定对象,且中途不可更改绑定对象

https://www.cnblogs.com/KaiMing-Prince/p/9741393.html//原理

内存申请及释放

new  <类型名> (初值) ;     //申请一个变量的空间

new  <类型名>[常量表达式] ;   //申请数组

如果申请成功,返回指定类型内存的地址;

如果申请失败,抛出异常,或者返回空指针(nullptr)。(C++11)

动态内存使用完毕后,要用delete运算符来释放。

delete   <指针名>;    //删除一个变量/对象

delete []  <指针名>;     //删除数组空间

int* p = new int(1); delete p;

int* p = new int[4]; delete [] p;

列表初始化不容许窄化

Int x { 1 };   Int x = { 1 };

C++的类型转化

const_cast:用于const及volatile修饰的变量的转换

static_cast:基本数据类型,非const,void*,继承层次的转换,不安全,无类型检查

dynamic_cast:用于多态继承层次的引用及指针上下转换,有安全检查,

reinpreter_cast:用于指针,引用,基本算数类型,函数指针,成员指针的转化

常量与指针

在前先读,在前不变

const int* x;//常量指针,指针指向常量

int* const y;//指针常量,指针为常量

有一个规则可以很好的区分const是修饰指针,还是修饰指针指向的数据——画一条垂直穿过指针声明的星号(*),如果const出现在线的左边,指针指向的数据为常量;如果const出现在右边,指针本身为常量。而引用本身与天俱来就是常量,即不可以改变指向

#define、typedef、using

#define是一个预处理指示符

#define TRUE 1 //结尾无分号,宏名为TRUE

typedef 创建在任何位置替代类型名的别名

typedef someType NewTypeName;

using替代typedef

using NewTypeName = someType;

基于范围的for循环语法

for( 元素名变量 : 广义集合) { 循环体 }

a.“元素名变量”可以是引用类型,以便直接修改集合元素的值;

b. “元素名变量”也可以是const类型,避免循环体修改元素的值

c. 其中“广义集合”就是“Range(范围)”,是一些元素组成的一个整体

避免头文件被多次包含

头文件中

#ifndef MY_HEADER_FELE_H

#define MY_HEADER_FELE_H

//头文件内容

#endif

#pragma once //c++03 预处理指令

_Pragma(“onece”) //c++11 VS2017不支持,替换(__pragma(once)) 运算符

内存分配

1、栈区(stack)——由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。(高地址到低地址)

2、堆区(heap)—— 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。(低地址到高地址)

3、全局区(静态区)(static)——全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 程序结束后由系统释放。 

4、文字常量区 —— 常量字符串就是放在这里的。 程序结束后由系统释放 。 

5、程序代码区 —— 存放函数体的二进制代码。

在堆中创建对象

Circle *pCircle1 = new Circle{}; //用无参构造函数创建对象

Circle *pCircle2 = new Circle{5.9}; //用有参构造函数创建对象

//程序结束时,动态对象会被销毁,或者

delete pObject;  //用delete显式销毁

对象数据

(1)声明方式1

Circle ca1[10];

(2)声明方式2

用匿名对象构成的列表初始化数组

Circle ca2[3] = { // 注意:不可以写成:auto ca2[3]=因为声明数组时不能用auto

Circle{3},

Circle{ },

Circle{5} }; 

(3)声明方式3

用C++11列表初始化,列表成员为隐式构造的匿名对象

Circle ca3[3] { 3.1, {}, 5 };

Circle ca4[3] = { 3.1, {}, 5 };

(4)声明方式4

用new在堆区生成对象数组

auto* p1 = new Circle[3];

auto p2 = new Circle[3]{ 3.1, {}, 5 };

delete [] p1;

delete [] p2;

p1 = p2 = nullptr;

构造函数初始化

在构造函数中用初始化列表初始化数据域

ClassName (parameterList)

  : dataField1{value1}, dataField2{value2}

{

  // Something to do

}

类的数据域是一个对象类型,被称为对象中的对象,或者内嵌对象,内嵌对象必须在被嵌对象的构造函数体执行前就构造完成

必须再构造函数初始化列表中完成

  1. 类的const成员常量
  2. 类的引用类型成员
  3. 没有默认构造函数的内嵌对象
  4. 如果存在继承关系,子类必须在初始化列表中调用父类的构造函数

类成员初始化次序

执行次序:就地初始化->初始化列表->在构造函数体中为成员赋值

哪个起作用(初始化/赋值优先级):在构造函数体中为成员赋值->构造函数初始化列表-> 就地初始化

若一个成员同时有就地初始化和构造函数列表初始化,则就地初始化语句被忽略不执行

不可变类

所有数据域均为私有

没有更改器函数

没有能够返回可变数据域对象的引用或指针的访问器

拷贝构造

拷贝构造:用一个对象初始化另一个同类对象(定义时调用)

如何声明拷贝构造函数(copy ctor)

Circle (Circle&);

Circle (const Circle&);

Invoke the copy ctor(调用拷贝ctor):

Circle c1( 5.0 );

Circle c2( c1 );    //c++03

Circle c3 = c1;     //c++03

Circle c4 = { c1 }; //c++11

Circle c5{ c1 };    //c++11

注意:

两个对象obj1和obj2已经定义。然后这种形式的语句:

obj1 = obj2;

不是调用拷贝构造函数,而是对象赋值。

拷贝构造函数调用的三种情况

  1. 一个对象以值传递的方式传入函数体
  2. 一个对象以值传递的方式从函数返回
  3. 对象需要通过另外一个对象进行初始化

隐式声明的拷贝构造函数

  1. 一般情况下,如果程序员不编写拷贝构造函数,那么编译器会自动生成一个
  2. 自动生成的拷贝构造函数叫做“隐式声明/定义的拷贝构造函数”
  3. 一般情况下,隐式声明的copy ctor简单地将作为参数的对象中的每个数据域复制到新对象中

析构函数

  1. 指针不会调用构造和析构函数
  2. 当对指针用new在内存中开辟空间的时候会调用构造函数
  3. 当我们使用new为指针开辟空间,然后用delete释放掉空间会调用构造和析构函数
  4. 当我们函数的形参是一个对象的时候,这时候会系统会调用拷贝构造函数析构函数
  5. 当我们函数的形参是一个引用的时候,这时候会系统不调用构造函数和析构函数

深拷贝与浅拷贝

浅拷贝:数据域是一个指针,只拷指针的地址,而非指针指向的内容

在两种情况下会出现浅拷贝

创建新对象时,调用类的隐式/默认构造函数

为已有对象赋值时,使用默认赋值运算符

深拷贝:拷贝指针指向的内容

如何深拷贝

  1. 自行编写拷贝构造函数,不使用编译器隐式生成的(默认)拷贝构造函数
  2. 重载赋值运算符,不使用编译器隐式生成的(默认)赋值运算符函数

  1. 数组结构化绑定

1)       cv-auto &/&&(可选) [标识符列表] = 表达式;

2)       cv-auto &/&&(可选) [标识符列表] { 表达式 };

3)       cv-auto &/&&(可选) [标识符列表] ( 表达式 );

4)       cv-auto: 可能由const/volatile修饰的auto关键字

5)       &/&& 左值引用或者右值引用

6)       标识符列表:逗号分隔的标识符

若初始化表达式为数组类型,则标识符列表中的名字绑定到数组元素

1)标识符数量必须等于数组元素数量

2)标识符类型与数组元素类型一致

int priArr [] {42, 21, 7};

     // ai/bi/ci 的基本类型都是int,只是cv标识或引用标识不同

  auto [a1, a2, a3] = priArr; // a1 是 priArr[0] 的拷贝,a2, a3类推

  const auto [b1, b2, b3] (priArr); // b1 是 priArr[0] 的只读拷贝,b2, b3类推

  auto &[c1, c2, c3] {priArr}; // c1 是 priArr[0] 的引用,c2, c3类推

  c3 = 14;                     // priArr[2]的值变为14



std::array stdArr = {'a','b','c'};

  auto [d1, d2, d3] {stdArr};

避免被继承

C++11引入final特殊标识符,可以使得类不能被继承

class B final {};

继承中的构造函数

派生类不继承的特殊函数

构造函数

析构函数

拷贝构造函数

赋值运算符函数

友元函数

继承基类构造函数

(1)     using A::A;  继承所有基类ctor

(2)     不能仅继承指定的某个基类ctor

继承中的名字隐藏

  1. 基类同名函数被隐藏
  2. 取消隐藏基类同名成员(using 声明语句可以将基类成员引入到派生类定义中)

多继承(菱形继承)

概念:A作为基类,B和C都继承与A。最后一个类D又继承于B和C,这样形式的继承称为菱形继承

菱形继承的缺点:

数据冗余:在D中会保存两份A的内容

访问不明确(二义性):因为D不知道是以B为中介去访问A还是以C为中介去访问A,因此在访问某些成员的时候会发生二义性

缺点的解决:

数据冗余:通过下面“虚继承”技术来解决

访问不明确(二义性):通过作用域访问符::来明确调用。虚继承也可以解决这个问题

虚继承

通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的A。

重定义与重载

重载函数:多个函数名字相同,但至少一个特征不同(参数类型、数量、特征)

重定义函数:函数特征相同,在基类和派生类中分别定义

多态

广义的多态:不同类型的实体/对象对于同一消息有不同的响应,就是OOP中的多态性

重载多态、子类型多态

用途:使用父类指针或引用可以访问子类对象成员。

联编:确定具有多态性的语句调用哪个函数的过程

静态联编:在程序编译时(Compile-time)确定调用哪个函数(函数重载

动态联编:程序运行时(Run-time),才能够确定调用哪个函数用动态联编实现的多态,(运行时多态

基类与派生类中有同名函数

(1)     通过派生类对象访问同名函数,是静态联编(对象是什么类型,就调什么类型

(2)     通过基类对象的指针访问同名函数,是静态联编(指针是什么类型,就调什么类型

(3)     通过基类对象的指针或引用访问同名虚函数,是动态联编(函数虚,不看指针/引用看真对象

虚函数的传递性

基类定义了虚同名函数,那么派生类中的同名函数自动变为虚函数

C++11引入override标识符,指定一个虚函数覆写另一个虚函数

C++11引入final特殊标识符,指定派生类不能覆写虚函数

抽象类与纯虚函数

抽象类:类太抽象以至于无法实例化就叫做抽象类

纯虚函数:要求子类实现

virtual double getArea() = 0;  // 在Shape类中

Circle子类必须实现getArea()纯虚函数才能实例化

动态类型转换

dynamic_cast 运算符

对于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。要深入了解内部转换的原理。

  1. 沿继承层级向上、向下及侧向转换到类的指针和引用
  2. 转指针:失败返回nullptr
  3. 转引用:失败抛异常
void printObject(Shape &shape)

{

  cout << "The area is "

       << shape.getArea() << endl;

  Shape *p = &shape;

  Circle *c = dynamic_cast<Circle*>(p);

  // Circle& c = dynamic_cast<Circle&>(shape); 

  // 引用转换失败则抛出一个异常 std::bad_cast

  if (c != nullptr) // 转换失败则指针为空

  {

    cout << "The radius is "

         << p1->getRadius() << endl;

    cout << "The diameter is "

         << p1->getDiameter() << endl;

  }

}

向上转换:将派生类类型指针赋值给基类类型指针(上转可不使用dynamic_cast而隐式转换

向下转换:将基类类型指针赋值给派生类类型指针(下转必须显式执行

基类对象和派生类对象的互操作

  1. 可将派生类对象截断,只使用继承来的信息
  2. 但不能将基类对象加长,无中生有变出派生类对象

运行时查询类型信息

typeid运算符(typeid用于获取对象所属的类的信息)

#include <typeinfo>  //使用typeid,需要包含此头文件

class A {};

A a{};

// ……

  auto& t1 = typeid(a);

  if (typeid(A) == t1) {

    std::cout << "a has type "

              << t1.name() << std::endl;

  }

C++对象模型

非继承下的C++对象模型

在此模型下,nonstatic 数据成员被置于每一个类对象中,而static数据成员被置于类对象之外。static与nonstatic函数也都放在类对象之外,而对于virtual 函数,则通过虚函数表+虚指针来支持

单继承

对于一般继承(这个一般是相对于虚拟继承而言),若子类重写(overwrite)了父类的虚函数,则子类虚函数将覆盖虚表中对应的父类虚函数(注意子类与父类拥有各自的一个虚函数表);若子类并无overwrite父类虚函数,而是声明了自己新的虚函数,则该虚函数地址将扩充到虚函数表最后。

一般多继承

  1. 子类的虚函数被放在声明的第一个基类的虚函数表中。
  2. overwrite时,所有基类的print()函数都被子类的print()函数覆盖。
  3. 内存布局中,父类按照其声明顺序排列。

菱形继承

会按顺序继承父类全部东西并存储。

单虚继承

  1. 虚继承的子类,如果本身定义了新的虚函数,则编译器为其生成一个虚函数指针(vptr)以及一张虚函数表。该vptr位于对象内存最前面。
  2. 虚继承的子类也单独保留了父类的vprt与虚函数表。这部分内容接与子类内容以一个四字节的0来分界。
  3. 虚继承的子类对象中,含有四字节的虚表指针偏移值。
  4. 虚基类表指针总是在虚函数表指针之后
  5. 虚基表存放偏移量,第一个条目存放虚基类表指针(vbptr)所在地址到该类内存首地址的偏移值。第二、第三...个条目依次为该类的最左虚继承父类、次左虚继承父类...的内存地址相对于虚基类表指针的偏移值。

菱形虚继承

  1. 在D类对象内存中,基类出现的顺序是:先是B1(最左父类),然后是B2(次左父类),最后是B(虚祖父类)
  2. D类对象的数据成员id放在B类前面,两部分数据依旧以0来分隔。
  3. 编译器没有为D类生成一个它自己的vptr,而是覆盖并扩展了最左父类的虚基类表,与简单继承的对象模型相同。
  4. 超类B的内容放到了D类对象内存布局的最后。

原文链接:https://www.cnblogs.com/QG-whz/p/4909359.html

路径

namespace fs = std::filesystem;

fs::path p1("d:\\cpp\\hi.txt"); // 字符串中的反斜杠要被转义

fs::path p2("d:/cpp/hi.txt");   // Windows也支持正斜杠

fs::path p3(R"(d:\cpp\hi.txt)");// 使用原始字符串字面量

this

不允许定义

不可取地址

不能赋值

指向对象

结构体内存对齐

对齐原则:

原则1:数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。

原则2:结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

原则3:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。

原文链接:https://blog.csdn.net/sssssuuuuu666/article/details/75175108

vector与List

Vector底层为数组,连续存储空间

支持随机访问,插入及删除不频繁的操作

vector一次性分配好内存,不够时才进行2倍扩容;

vector 容器扩容的过程需要经历以下 3 步:

完全弃用现有的内存空间,重新申请更大的内存空间;

将旧内存空间中的数据,按原有顺序移动到新的内存空间中;

最后将旧的内存空间释放。

List底层为双向链表,存储不连续

支持高效的插入删除操作及不频繁的随机访问

list每次插入新节点都会进行内存申请。

Vector中的size()、capacity()、resize()、reserve()

  1. size()告诉你容器中有多少元素。它没有告诉你容器为它容纳的元素分配了多少内存。
  2. capacity()告诉你容器在它已经分配的内存中可以容纳多少元素。
  3. resize(Container::size_type n)强制把容器改为容纳n个元素。调用resize之后,size将会返回n。如果n小于当前大小,容器尾部的元素会被销毁。如果n大于当前大小,新默认构造的元素会添加到容器尾部。如果n大于当前容量,在元素加入之前会发生重新分配。
  4. reserve(Container::size_type n)强制容器把它的容量改为至少n,提供的n不小于当前大小。这一般强迫进行一次重新分配,因为容量需要增加。(如果n小于当前容量,vector忽略它,这个调用什么都不做,string可能把它的容量减少为size()和n中大的数,但string的大小没有改变。

通常有两情况使用reserve来避免不必要的重新分配。第一个可用的情况是当你确切或者大约知道有多少元素将最后出现在容器中。那样的话,就像上面的vector代码,你只是提前reserve适当数量的空间。第二种情况是保留你可能需要的最大的空间,然后,一旦你添加完全部数据,修整掉任何多余的容量。

Set、multiset、map、multimap

底层实现均为红黑树,即红黑树就是一种平衡的二叉查找树。其所有操作都可以在O(logn)时间范围内完成。另外,基于红黑树的map在通过迭代器遍历时,得到的是key按序排列后的结果,这点特性在很多操作中非常方便。

特点:

  1. 所有节点非红即黑
  2. 根结点为黑
  3. 每个叶子节点为黑色的空节点(null)
  4. 每个红节点的子节点均为黑色
  5. 从任意节点到其每个叶子的所有路径都包含相同的黑色节点

set和map特性和区别

set是一种关联式容器,其特性如下:

  1. set以RBTree作为底层容器
  2. 所得元素的只有key没有value,value就是key
  3. 不允许出现键值重复
  4. 所有的元素都会被自动排序
  5. 不能通过迭代器来改变set的值,因为set的值就是键

map和set一样是关联式容器,它们的底层容器都是红黑树,区别就在于map的值不作为键,键和值是分开的。它的特性如下:

  1. map以RBTree作为底层容器
  2. 所有元素都是键+值存在
  3. 不允许键重复
  4. 所有元素是通过键进行自动排序的
  5. map的键是不能修改的,但是其键对应的值是可以修改的

unordered_map,unordered_set

哈希表,查找O(1),无序

哈希冲突:

  1. 开放地址法:构造哈希函数时,增加一个偏移量(di),分为线性探测(i)、平方探测(+-i²)、双散列(di=i*和(key))。线性探测有可能产生聚集现象,平方探测有可能遍历不到每个空位,解决方法容量为素数(4k+3)。
  2. 链地址法:当发生冲突时,用单链表链接。

volatile

  1. volatile定义变量的值是易变的,每次用到这个变量的值的时候都要去重新读取这个变量的值,而不是读寄存器内的备份。
  2. 多线程中被几个任务共享的变量需要定义为volatile类型。

explicit

C++中的explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式)。

智能指针

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

https://www.cnblogs.com/WindSun/p/11444429.html

const

  1. const常量的初始化必须在构造函数初始化列表中初始化,而不可以在构造函数体内初始化
  2. 任何不修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或调用了其他非const成员函数,编译器就会指出错误。
  3. 同函数名、参数、返回值可以仅通过是否为const来定义为类的两个成员函数。在调用时,const对象调用const成员函数,非const对象调用非const成员函数。
  4. const返回类型只有在修饰指针或引用是才有用,单凭const返回类型不可以重载。

计算类的大小

空类大小为1字节

  1. 非静态成员数据之和
  2. 虚基表指针(虚继承),虚指针(虚函数),
  3. 字节对齐加入的padding

接口的定义及实现

所谓的接口,即将内部实现细节封装起来,外部用户用过预留的接口可以使用接口的功能而不需要知晓内部具体细节。C++中,通过类实现面向对象的编程,而在基类中只给出纯虚函数的声明,然后在派生类中实现纯虚函数的具体定义的方式实现接口,不同派生类实现接口的方式也不尽相同,从而实现多态。

String

int转string

string转int

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值