个人技术分享

常见字符

 *

  1. 注释:/* 这是一个注释*/
  2. 乘法:a * b
  3. 取值运算符(间接值/解除引用/解引用运算符):*指针变量,int a = 4;int *p=&a; *p
  4. 指针变量:数据类型 *变量名, int *no = &bh,代表了no是一个指针
  5. 数据类型:int*:整型指针类型、char* 字符指针类型,char* c=(char *) &b。int no=38,int* ptr=&no, &no和ptr是一个东西,no和*ptr是一个东西。

注意:int *no 和 int* no什么区别

 1. 实际上没有区别
 2. int* a, b; 这里 a 是指向 int 的指针,而 b 只是一个普通的 int 整数变量。
 3. int *a, *b; 这样的声明清晰地表明 a 和 b 都是指向 int 的指针。

::

  1. 变量属于哪个域:std::count<<"no"
  2. 当局部变量和全局变量名称相同时,会屏蔽全局变量使用局部变量,如果想使用全局变量,使用::,:: a。

常见关键词 

typedef

数据类型的别名

static(静态)

静态局部变量

  1.  主要作用:会影响变量的存储期和作用域。
  2. 只会被初始化一次
  3. 静态局部变量:static修饰的局部变量生命周期和程序相同。即在函数调用结束后不会被销毁,而是保持其值直到下次调用。
  4. 静态类成员:用 static 声明的类成员属于整个类,而不是类的各个实例。

const(常量)

  1. 主要作用:增强程序的安全性
  2. 初始化之后,值不能被修改
  3. 当 const 修饰类的成员函数时,表示该函数不会修改类的任何成员变量

指针

  1.  常量指针:const 数据类型 *变量名;不能通过解引用的方法修改内存地址中的值(用原始的变量名是可以修改的)。
    1. Int a=3,b=4;

      const int* p=&a; #报错

      *p = 13; p=&b; #不报错

  2. 指针常量
    1. 数据类型 * const 变量名;
    2. 指向的变量(对象)不可改变。
    3. 在定义的同时必须初始化,否则没有意义。
    4. 可以通过解引用的方法修改内存地址中的值。
    5. Int a=3,b=4;

      const int* p=&a; #不报错

      *p = 13; p=&b; #报错

    6. 新名字:引用

  3. 常指针常量

    1. const 数据类型 * const 变量名;

    2. 指向的变量(对象)不可改变,不能通过解引用的方法修改内存地址中的值。

    3. 新名字:常引用。 

    4. 常量指针:指针指向可以改,指针指向的值不可以更改。

      指针常量:指针指向不可以改,指针指向的值可以更改。

      常指针常量:指针指向不可以改,指针指向的值不可以更改。

      记忆秘诀:*表示指针,指针在前先读指针;指针在前指针就不允许改变。

      常量指针:const 数据类型 *变量名

      指针常量:数据类型 * const 变量名

 void

  1. 函数的返回值用void,表示函数没有返回值。
  2. 函数的参数填void,表示函数不需要参数(或者让参数列表空着)。
  3. 函数的形参用void *,表示接受任意数据类型的指针。
  4. 注意:
    1. 不能用void声明变量,它不能代表一个真实的变量,但是,用void *可以。
    2. 不能对void *指针直接解引用(需要转换成其它类型的指针)。
    3. 把其它类型的指针赋值给void*指针不需要转换。
    4. 把void *指针赋值给把其它类型的指针需要转换。

内存

内存分为内核空间和用户空间,内核空间由操作系统管理,与程序员没什么关系。我们写的程序运行在用户空间。一个c++源程序编译成可执行程序后,二进制文件的大小是固定的,最多几十兆。

程序运行时,内存主要分成四个区,分别是栈、堆、数据段和代码段。

1)栈:存储局部变量函数参数和返回值。不管计算机的内存是8G还是16G,分配给栈的只有几兆。修改系统参数可以调整栈的大小。

2)堆:存储动态开辟内存的变量。内存越大,分配的内存就越大。

3)数据段:存储全局变量和静态变量。

4)代码段:存储可执行程序的代码和常量(例如字符常量),此存储区不可修改

注意:栈和堆的主要区别:

1)管理方式不同:栈是系统自动管理的,在出作用域时,将自动被释放;堆需手动释放,若程序中不释放,程序结束时由操作系统回收。

2)空间大小不同:堆内存的大小受限于物理内存空间;而栈就小得可怜,一般只有8M(可以修改系统参数)。

3)分配方式不同:堆是动态分配;栈有静态分配和动态分配(都是自动释放)。

4)分配效率不同:栈是系统提供的数据结构,计算机在底层提供了对栈的支持,进栈和出栈有专门的指令,效率比较高;堆是由C++函数库提供的。

栈的效率非常高,堆是用链表来管理的,效率会低一点。

5)是否产生碎片:对于栈来说,进栈和出栈都有着严格的顺序(先进后出),不会产生碎片;而堆频繁的分配和释放,会造成内存空间的不连续,容易产生碎片,太多的碎片会导致性能的下降。

6)增长方向不同:栈向下增长,以降序分配内存地址;堆向上增长,以升序分配内存地址。

空指针

  1. 用0或NULL都可以表示空指针。int* p=0,表示还没有指向任何地址。
  2. 如果对空指针使用delete运算符,系统将忽略该操作,不会出现异常
  3. 用0和NULL表示空指针会产生歧义,C++11建议用nullptr表示空指针,也就是(void *)0。

野指针

出现野指针的情况主要有三种:

1)指针在定义的时候,如果没有进行初始化,它的值是不确定的(乱指一气)。

2)如果用指针指向了动态分配的内存,内存被释放后指针不会置空,但是,指向的地址已失效。

3)指针指向的变量已超越变量的作用域(变量的内存空间已被系统回收),让指针指向了函数的局部变量,或者把函数的局部变量的地址作为返回值赋给了指针。

规避方法:

1)指针在定义的时候,如果没地方指,就初始化为nullptr。

2)动态分配的内存被释放后,将其置为nullptr。

3)函数不要返回局部变量的地址。

注意:野指针的危害比空指针要大很多,在程序中,如果访问野指针,可能会造成程序的崩溃。是可能,不是一定,程序的表现是不稳定,增加了调试程序的难度。

函数指针

  1. 声明函数指针;
    1. 函数指针的声明是:

      int (*pfa)(int,string);

      bool (*pfb)(int,string);

      bool (*pfc)(int);

      pfa、pfb、pfc是函数指针名
  2. 让函数指针指向函数的地址;
    1. 函数指针的赋值:函数指针名=函数名;

  3. 通过函数指针调用函数。
    1. (*函数指针名)(实参); C

      函数指针名(实参); C++

数组

初始化:

1)数据类型 数组名[数组长度] = { 值1,值2,值3, ...... , 值n};

2)数据类型 数组名[ ] = { 值1,值2,值3, ...... , 值n};

3)数据类型 数组名[数组长度] = { 0 };  // 把全部的元素初始化为0。

4)数据类型 数组名[数组长度] = { };    // 把全部的元素初始化为0。

数组相关函数

sizeof(数组名):可以得到整个数组占用内存空间的大小

memset():把数组中全部的元素清零。void *memset(void *s, int c, size_t n);

memcpy():把数组中全部的元素复制到另一个相同大小的数组。头文件#include <string.h>。函数原型:void *memcpy(void *dest, const void *src, size_t n);

数组的地址

a)数组在内存中占用的空间是连续的。

b)C++将数组名解释为数组第0个元素的地址。

c数组第0个元素的地址和数组首地址的取值是相同的。

double a[5];
cout << "a的值是:" << (long long) a << endl;
cout << "&a的值是:" << (long long)&a << endl;
cout << "a[0]的地址是:" << (long long) &a[0] << endl;
/*这三个值一样*/

指针的数组表示

在C++内部,用指针来处理数组。

C++编译器把   数组名[下标]  解释为  *(数组首地址+下标)

C++编译器把   地址[下标]  解释为  *(地址+下标)

int a[5] = [3,5,6,7,8,9,10,11];
int *p = a;

for(int ii=0; ii<5;ii++)
{
    count<<"a["<<ii<<"的值是:"<<a[ii]<<end;
    count<<"*(p+"<<ii<<")的值是:"<<*(p+ii)<<end;
    count<<"p["<<ii<<"的值是:"<<p[ii]<<end;
}

一维数组的排序qsort

void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));

  1. 为什么需要第三个形参size_t size
    1. 在函数内部不是按照函数类型操作的,而是按照内存块来操作的,交换两个元素,不是用赋值,而是memcpy()函数。
  2. size_t是C标准库中定义的,在64位系统中是8字节无符号整型(unsigned long long)。typedef unsigned long long size_t

动态分配内存new

用new动态创建变量

  1. 申请内存的语法:new 数据类型(初始值);  new int(5) // C++11支持{}
  2. 如果动态分配的内存不用了,必须用delete释放它,否则有可能用尽系统的内存。

用new动态创建一维数组

1)数组的内存:普通数组在栈上分配内存,栈很小;如果需要存放更多的元素,必须在堆上分配内存。

2)创建:动态创建一维数组的语法:数据类型 *指针=new 数据类型[数组长度];

3)释放:释放一维数组的语法:delete [] 指针;

注意:

  1. 不要用delete[]来释放不是new[]分配的内存。
    1. C语言的malloc()函数也可以动态分配内存,malloc()函数分配的内存要用free()函数来释放,不能用delete。
    2. 指针指向的地址是栈上的变量,普通变量的地址也不能用delete。int a[1000]; a[1000]=8;
  2. 不要用delete[]释放同一个内存块两次(否则等同于操作野指针)。
  3. 对空指针用delete[]是安全的(释放内存后,应该把指针置空nullptr)。
    1. 不写[],只会释放第0个元素的内存空间
  4. 如果内存不足,调用new会产生异常,导致程序中止;如果在new关键字后面加(std::nothrow)选项,则返回nullptr,不会产生异常。int a[100000]; a[100000]=8;内存不足,会崩溃掉。int *a = new int[1000000000]; a[10000000000]=8;内存不足,会崩溃掉。
for (int ii = 1; ii > 0; ii++)
	{
		int* p = new int[100000];     // 一次申请100000个整数。
		cout << "ii="<<ii<<",p=" << p << endl;
	}

内存会螺旋式越来越大,(一会大一会小,是因为系统有交换区)

C风格的字符串

  1. string是C++的类,封装了C风格的字符串。
  2. string能自动扩展,不用担心内存问题。
  3. C语言约定:如果字符型(char)数组的末尾包含了空字符\0(也就是0),那么该数组中的内容就是一个字符串。

  4. 一个中文汉字占两个字节。
  5. string str='XYZ', str[0]显示字符,int(str[0]) 显示ascii码。

初始化

char name[11]; // 可以存放10个字符,没有初始化,里面是垃圾值。

char name[11] = "hello"; // 初始内容为hello,系统会自动添加0

char name[] = { "hello" }; // 初始内容为hello,系统会自动添加0,数组长度是6。

char name[11] = { "hello" }; // 初始内容为hello,系统会自动添加0

char name[11] { "hello" }; // 初始内容为hello,系统会自动添加0。C++11标准。

char name[11] = { 0 }; // 把全部的元素初始化为0。

注:

char name[11];

count<<"name="<<name<<end;

11个字节,一个汉字是两个字节,应该显示5个烫,为什么显示这么多?

不管是C还是C++,处理字符串的时候,从字符数组的起始地址开始,遇到0才结束。没遇到0就继续找。

相关函数

  1. memset(name,0,sizeof(name));   // 把全部的元素置为0。

  2. strcpy()字符串复制或赋值。

    char *strcpy(char* dest, const char* src);

    功 能: 将参数src字符串拷贝至参数dest所指的地址。注:C++风格的字符串可以用等号赋值,C风格的字符串不能用等号赋值,要用strcpy。

  3.  strncpy()字符串复制或赋值

  4. strlen()获取字符串的长度

  5. strcat()字符串拼接

  6. strncat()字符串拼接

  7. strcmp()和strncmp()字符串比较

  8. strchr()和strrchr()查找字符

  9. strstr()查找字符串

  10. 注意事项:

    1. 操作字符串的函数,遇到0字符串才结束,不会判断数组是否越界,因为操作字符串的函数行参是指针,只存放了字符串的起始地址,没有数组长度参数。

    2. 不要在子函数中对字符指针用sizeof运算,因为得到的是指针占用内存空间的大小,都是8字节,不是数组的大小。所以,不能在子函数中对传入的字符串进行初始化,除非字符串的长度也作为参数传入到了子函数中。

二维数组

行指针(数组指针)

声明行指针的语法:数据类型 (*行指针名)[行的大小];  // 行的大小即数组长度。

int (*p1)[3];         // p1是行指针,用于指向数组长度为3的int型数组。

double (*p3)[5];  // p3是行指针,用于指向数组长度为5的double型数组。

注:

int (*p2) [5]: 行指针

int* p2[5] 指针数组

结构体

函数

  1. 清空结构体:memset(&stgirl, 0, sizeof(stgirl));可以把结构体中全部的成员清零。
  2. 清空结构体:bzero(&stgirl, sizeof(stgirl));可以把结构体中全部的成员清零。
  3. 复制结构体:memcpy:把结构体中全部的元素复制到另一个相同类型的结构体.
  4. 复制结构体:=

链表

  1. 都是动态分配内存,没有静态一说。

重载(派生类使用基类函数

继承

  1. 友元函数不是类成员,不能继承。

构造函数/析构函数

  1. 构造函数不能继承,创建派生类对象时,先执行基类构造函数,再执行派生类构造函数。

  2. 析构函数不能继承,而销毁派生类对象时,先执行派生类析构函数,再执行基类析构函数。

构造基类

  1. 基类构造函数负责初始化被继承的数据成员(基类成员变量必须有基类的构造函数初始化);派生类构造函数主要用于初始化新增的数据成员。
    1. 基类的私有成员在派生类不可见,派生类没办法初始化基类的私有成员。
    2. 所有派生类构造函数都初始化基类数据,属于重复,不符合继承的理念。
  2.   

访问权限

公有继承
各成员 派生类中 基类与派生类外
基类的公有成员 直接访问 直接访问
基类的保护成员 直接访问 调用公有函数访问
基类的私有成员 调用公有函数访问 调用公有函数访问
从基类继承的公有成员 直接访问 直接访问
从基类继承的保护成员 直接访问 调用公有函数访问
从基类继承的私有成员 调用公有函数访问 调用公有函数访问
派生类中定义的公有成员 直接访问 直接访问
派生类中定义的保护成员 直接访问 调用公有函数访问
派生类中定义的私有成员 直接访问 调用公有函数访问
私有继承

第一级派生类中 第二级派生类中 基类与派生类外
基类的公有成员 直接访问 不可访问 不可访问
基类的保护成员 直接访问 不可访问 不可访问
基类的私有成员 调用公有函数访问 不可访问 不可访问

多态(基类使用派生类函数)(基类指针可以指向派生类对象)

  1. 基类指针只能调用基类的成员函数,不能调用派生类的成员函数。

  2. 有了virtual虚函数,基类指针指向基类对象时就使用基类的成员函数和数据,指向派生类对象时就使用派生类的成员函数和数据,

virtual/虚函数

  1. 在基类的成员函数前加virtual 关键字,把它声明为虚函数,基类指针就可以调用派生类中同名的成员函数,通过派生类中同名的成员函数,就可以访问派生对象的成员变量。
基类调用派生类情况
virtual函数 基类指针指向派生类对象,调用派生类函数
virtual函数

基类指针指向派生类对象,加基类的域,调用基类函数

virtual函数 基类指针指向派生类对象,调用基类函数

纯虚函数

  1. 纯虚函数只有函数名、参数和返回值类型,没有函数体,具体实现留给该派生类去做。

抽象类

  1. 含有纯虚函数的类被称为抽象类,不能实例化对象,可以创建指针和引用。

  2. 派生类必须重定义抽象类中的纯虚函数,否则也属于抽象类。

dynamic_cast

运行阶段类型识别。语法:派生类指针 = dynamic_cast<派生类类型 *>(基类指针);

dynamic_cast只适用于包含虚函数的类。因为要查虚函数表。

final

  1. 限制某个类不能被继承
  2. 或者某个虚函数不能被重写

override

  1. 重写基类的虚函数

#pragma once

#pragma once用来防止某个头文件被多次include。只要在头文件的最开始加入这条指令就能够保证头文件被编译一次。

#ifndef,#define,#endif用来防止某个宏被多次定义。

内联函数

在C++中,内联函数可代替有参数的宏,效果更好。

宏和const

结构体和类

STL

逻辑结构 物理结构 底层
string
list 链表
vector 数组
pair
map
unordered map 数组+链表
queue 队列 数组或链表
array 静态数组
deque 双端队列
forward_list 单链表
multimap 红黑树
set&multiset

红黑树

unordered_multimap
unordered_set&unordered_multiset
priority_queue 有权值的单向队列queue dequelist
stack dequelist

vector

封装的是数组

list

封装的是链表

pair

  1. 类模板
  2. 实现是结构体
  3. make_pair()返回的临时对象。//auto p5 = make_Pair<int, string>(5, "西施5");  

红黑树

  1. 如果数据量很小,数组+二分查找法;如果数据量很大(几万),用红黑树。;如果数据量达到了上千万

map

  1. 封装了红黑树(平衡二叉排序树)
  2. ​​​​​​​map容器的元素是pair键值对。

unordered_map哈希表

  1. 容器封装了哈希表
  2. 数组+链表
  3. 效率
    1. 插入不需要比较
    2. 删除元素与查找元素的效率相同

queue

  1. queue容器的逻辑结构是队列,物理结构可以是数组或链表
  2. queue容器不支持迭代器。

智能指针

unique_ptr

  1. 独享它指向的对象
  2. 因此没有赋值和拷贝功能,因为要独享。
  3. 不要用同一个裸指针初始化多个unique_ptr对象。

shared_ptr

  1. 共享它指向的对象
  2. make_shared:std::make_shared 是一种用于创建 std::shared_ptr 的便利函数

weak_ptr

裸指针 vs智能指针

  1. 裸指针
    1. 内存泄漏:内存没有被正确释放
    2. 悬空指针(dangling pointer):指针所指向的内存被释放后,指针未置为 nullptr
    3. 多次释放等:同一个指针被多次释放,通常会导致程序崩溃。
  2. 智能指针

auto

  1. 自动推导类型

explicit

  1. 关闭类型自动转换的特性

常见知识点

运行C++程序

Mac运行cpp程序:g++ -o demo demo.cpp

标准

  1. C++98
  2. C++11现在用的比较多
  3. C++14:落地要过几年

内存

位置
智能指针