c++查漏补缺

from 牛客网 and c++ primer

strcpy函数实现 

使用assert 判断 是否为NULL

使用while赋值

返回首位地址

char * strcpy( char *strDest, const char *strSrc )
{
 assert( (strDest != NULL) && (strSrc != NULL) );
 char *address = strDest;
 while( (*strDest++ = * strSrc++) != ‘\0’ );
 return address;}

野指针

 “野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。野指针的成因主要有两种:

(1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

(2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块。

free()释放的是指针指向的内存!注意!释放的是内存,不是指针!这点非常非常重要!指针是一个变量,只有程序结束时才被销毁。释放了内存空间后,原来指向这块空间的指针还是存在!只不过现在指针指向的内容的垃圾,是未定义的,所以说是垃圾。因此,前面我已经说过了,释放内存后把指针指向NULL,防止指针在后面不小心又被解引用了。非常重要啊这一点!

  在使用指针的时候还要注意的问题:

  (1)不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放.

(2)在使用指针进行内存操作前记得要先给指针分配一个动态内存。

sizeof 总结

宏定义 

本质不是函数 变量替换
为什么标准头文件都有类似以下的结构?

#ifndef __INCvxWorksh

#define __INCvxWorksh

#ifdef __cplusplus
extern “C” {

#endif
/…/

#ifdef __cplusplus
}

#endif
#endif / __INCvxWorksh /

头文件中的编译宏

#ifndef __INCvxWorksh

#define __INCvxWorksh

#endif

的作用是防止被重复引用。
作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在symbol库中的名字与C语言的不同。例如,假设某个函数的原型为:
void foo(int x, int y);
该函数被C编译器编译后在symbol库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。_foo_int_int这样的名字包含了函数名和函数参数数量及类型信息,C++就是考这种机制来实现函数重载的。
为了实现C和C++的混合编程,C++提供了C连接交换指定符号extern “C”来解决名字匹配问题,函数声明前加上extern “C”后,则编译器就会按照C语言的方式将该函数编译为_foo,这样C语言中就可以调用C++的函数了。

内联函数和宏定义的区别?

内联函数和宏很类似,而区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。

‘\0’的位置 

举例子 编写一个函数,作用是把一个char组成的字符串循环右移n个。

 void LoopMove(char *str, int steps)
{
    char tmp[MAXSIZE];
    int len = strlen(str);
    strcpy(temp, str+len-steps);
    strcpy(temp+steps, str);
    *(temp+len) = '\0';
    strcpy(str, temp);}

String 原型

class String
{
public:
    String(const char *str = NULL);
    String(const String &other);
    ~String(void);
    String &operator = (const String &other);
private:
    char *m_data;};
String::String(const char *str)
{
if(str == NULL)
{
    m_data = new char[1];
    *m_data = '\0';
}else{
    int len = strlen(str);
    m_data = new char[len+1];
    assert(m_data != NULL);
    strcpy(m_data, str);
}}

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

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

String & String::operator =(const String &other)
{
    if(this == &other)
    return *this;
    delete [] m_data;
    int len = strlen(other.m_data);
    m_data = new char[len+1];
    assert(m_data != NULL);
    strcpy(m_data, other.m_data);
    return *this;}

static and const

static关键字至少有下列n个作用:

(1)函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;

(2)在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;

(3)在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;

(4)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;

(5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。

const关键字至少有下列n个作用:

(1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;

(2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;

(3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;

(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的 成员变量;

(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。例如:
const classA operator(const classA& a1,const classA& a2);
operator的返回结果必须是一个const对象。如果不是,这样的变态代码也不会编译出错:
classA a, b, c;
(a b) = c; // 对ab的结果赋值
操作(a * b) = c显然不符合编程者的初衷,也没有任何意义。

const
char * const cp;
cp is a const pointer to char

const char * p;
p is a pointer to const char;

判断大端法和小端法

int checkCPU()
{{
    union w
    {
    int a;
    char b;} c;
    c.a = 1;
    return (c.b == 1);}}

函数
int *matrix[10]; 10个指针构成的数组

int (*matrix)[10]; 指向含有10个整数的数组的指针

int main(int argc , char *argv[])  第一个argc表示数组中字符串的数量  第二个argv数组存储输入的字符串 argv[0]是程序的名称

可变新参的函数

initializer_list lst initializer_list对象中的元素是常量
eg:

void error_msg(initializer_list<string> il)
{
    for(auto beg=il.begin();beg!=il.end();++beg)
    cout<<*beg<<" ";
    cout<<endl;}

函数返回值 不要返回局部对象的引用或指针
引用返回函数可以作为左值

重载 同一个作用域内 函数明相同 形参不同

lambda表达式

值捕获 创建时拷贝,如下例子,另外加上关键字mutable可修改变量

引用捕获 是引用变量,如下例子

隐式捕获 在捕获列表中添加=或&。=表示值捕获,&表示引用捕获, 如下例子

智能指针

shared_ptr 允许多个指针指向同一个对象,每个都有一个关联的计数器 通常称为引用计数,拷贝一个shared_ptr 计数器会递增.

定义: 

shared_ptr p = make_shared(10, ‘9’); // p指向一个值为”9999999999”的string

shared_ptr的折构函数会递减它所指向的对象的引用技术。如果引用技术为0,shared_ptr的折构函数就会销毁对象,并释放它占用的内存。反之,它仍然没有销毁,仍能使用。

使用动态生存期的资源的类的原因: 

(1) 程序不知道自己需要使用多少对象

(2) 程序不知道所需对象的准确类型 

(3) 程序需要在多个对象间共享数据

shared_ptr不直接支持管理动态数组,如果希望使用shared_ptr管理一个动态数组,必须定义自定的删除器
shared_prt sp(new int[10], {delete []p;});

动态内存常见问题: 忘记delete内存,使用已经释放的对象,同一会内存释放两次。delete后需要将值置空(nullptr)

unique_ptr “独占”所指向的对象

weak_ptr 指向一个shared_ptr管理的对象 不影响它的引用计数

allocator 将内存分配和对象构造分离开来,分配的内存是未构造的。
eg:
allocator alloc;
auto const p = alloc.allocate(n);

拷贝发生的情况

拷贝函数 形参必须是引用类型 函数返回非引用类型对象才能成功拷贝

(1) 将对象作为实参传递给非引用形参

(2) 作为返回类型为非引用类型的函数返回一个对象

(3)用花括号初始化一个数组中的元素或一个聚合类中的成员

(4)赋值=

一般而言 一个左值表达式是一个对象的身份 而右值表达式表示的是对象的值 左值持久 右值短暂

&&i 右值引用 必须绑定到右值的引用

虚函数

成员函数没有声明为虚函数 则其解析过程发生在编译时而非运行时 对虚函数的调用可能在运行时才被解析

静态类型 在编译时是已知的 动态类型是变量或者是表示内存中对象的模型 知道运行时才知道

c++ 多态性 我们把具有继承关系的多个类型称为多态类型 使用虚函数实现多态 当我们使用基类的引用或者指针调用基类定义的一个虚函数 我们不知道调用的类型是什么 是基类的对象还是派生类的对象 虚函数直到运行时才决定是哪个版本 非虚函数是在编译时绑定的

override 提示覆盖基类虚函数 帮助编译器识别错误 final 不允许它的派生类覆盖final修饰的函数

可以使用作用域运算符 指定访问的函数 回避虚函数机制

纯虚函数 没有定义的函数 在类内使用=0定义 若要对纯虚函数进行定义 需要在类外定义

含有纯虚函数的类是抽象基类 抽象基类负责定义接口 不能直接创建抽象基类的对象 派生类需要定义纯虚函数 否则仍然是抽象基类

访问控制与继承

访问控制:

public:定义为public的成员对普通用户、类的实现者、派生类都是可访问的。public通常用于定义类的外部接口。

protected:定义protected成员的目的是让派生类可以访问而禁止其他用户访问, 派生类的成员或友元只能通过派生类来访问基类的受保护成员,不能直接访问基类成员。

private:定义为private的成员只能被类的实现者(成员和友元)访问。private部分通常用于封装(即隐藏)类的实现细节。

继承:

public:如果继承是公有的,则成员将遵循其原有的访问说明符。父类中的public、protected和private属性在子类中不发生改变。

protected:比protected级别高的访问权限会变成protected。即父类中的public属性在子类中变为protected,父类中的protected和
private属性在子类中不变。

private:比private级别高的访问权限会变成private。即父类中的三种访问属性在子类中都会变成private。不影响子类对父类protected属性的访问

友元关系不能够被继承

使用using 改变个别成员的可访问性:

struct 默认 public 继承

class 默认 private 继承

在编译时查找 在对象中调用函数 若找不到该函数 往父类上去找

派生类的成员和基类的某个成员同名 则派生类将在其作用域隐藏该基类成员

虚析构函数

确保执行正确版本的析构函数 如果基类的析构函数不是虚函数 则delete一个指向派生类对象的基类指针将产生未定义行为。

基类或派生类的合成拷贝控制成员的行为与其他合成的构造函数、赋值运算符类似

模板

函数模板:

template

int compare(const T &v1, const T &v2)

{

     if(v1 < v2 ) return -1;

     if(v1 > v2) return 1;

     return 0;}

实例化模板 compare(0, 1); T 为 int

unsigned 定义非类型参数 非类型参数表示一个值而非一个类型

与非模板代码不同 模板的头文件既包含声明也包含定义 编译错误多发生在模板实例化,有未定义行为

类模板:

template   class Test{

private:

    T n;
    const T i;
public:
    Test():i(0) {}
    Test(T k);
    ~Test(){}
    void print();
    T operator+(T x); };

能定义友元 使用using定义别名

一个类(普通类 类模板)可以包含本身是模板的成员函数。这种成员函数称为成员模板 ,不能是虚函数

特殊标准库

tuple 可以拥有任意数量的成员

bitset 方便处理二进制的类模板

正则表达式

随机数 分布类型 随机数引擎 设置随机数发生器种子

设置浮点数精度 cout.precision() 或 setprecision

大型程序工具

(1) 异常处理
栈展开 栈展开过程沿着嵌套函数的调用链不断查找,知道找到异常匹配的catch子句为止 没有找到匹配的退出主函数。通俗的讲在本函数没有找到catch 继续到外层找,直到找到为止,找不到退出函数

noexcept 说明 指定某个函数不会抛出异常 常见两种情况下使用 一是我们确认函数不会抛出异常 二是我们根本不知道该如何处理异常

若有函数声明了不会抛出异常 但实际上还是抛出 程序会调用terminate 终止执行

异常类层次

(2)命名空间
全局命名空间的分割 每个命名空间是一个作用域 定义可以不连续
(3)多重继承
解决多重继承中不同基类拥有相同名称的成员的方法:在派生类为该成员重新自定义

虚继承:解决继承同个基类多次的问题
虚基类:共享的基类对象

含虚基类的构造顺序 首先使用提供给最低层派生类的构造函数的初始值初始化该对象的虚基类子部分,接下来按照直接基类在派生类列表中出现的次序依次对其初始化

没有显示初始化虚基类 则虚基类默认构造函数被调用

特殊工具和技术

(1) new delete
new 调用operator new标准库函数,分配空间 运行相应构造函数 对象被分配了空间并构造完成后返回一个指向该对象的指针

delete 执行指针指向对象的析构函数 调用operator delete 的标准库函数 释放内存。

malloc  分配指定大小(size_t)的空间,返回指向分配空间的指针
free 将相关内存放回系统

调用析构函数会销毁对象 但是不会释放空间
定位new表达式 允许我们在一个特定、余弦分配的内存地址上构造对象

(2) RTTI 运行时类型识别
由两个运算符实现 对定义了虚函数的类有效
typeid运算符 用于返回表达式的类型
dynamic_cast运算符 用于将基类的指针或引用安全地转换成派生类的指针或引用

使用RTTI 为具有继承关系的类实现相等运算符 typeid 判断是否同一种类型 类型自定义equal比较成员变量

(3) 枚举类型

enum class peppers{red, yellow, green} 限定作用域的枚举类型 可以自动转换成整形

enum colors{red. yellow, green} 不限定作用域的枚举类型 不作隐式转换

适用于检验判别式
(4) 嵌套类
嵌套类和外层类相互独立 嵌套类在外层类作用域内可见 外层类作用域之外不可见
(5) union
一种特殊的类 可以有多个数据成员 共享同一内存空间 任意时刻只有一个数据成员可以有值。 可以包含类类成员

(6)局部类
定义在某个函数内部 所有成员必须定义在类的内部 一般复杂度不高 不允许定义静态变量 智能访问外层作用域的类型名 静态变量和枚举变量

(7) 固有的不可移植特性

位域 Bit 位域类型必须是整型或枚举类型 任何指针都无法指向类的位域

volatile 直接处理硬件的程序包含这样的数据元素,它们的值由程序之外的过程控制, 应该将该对象声明为volatile

(8) 链接指示 extern “C”
c++ 有时需要调用其他程序编写的函数 最常见的是调用C语言编写的函数。

有两种形式 单个或符合

eg:

extern "C" size_t strlen(const char *);
extern "C" {
    int strcmp(const char*, const char *);
    char *strcat(char*,const char *);}

c++内存泄露

内存泄漏(memory leak)是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。

出现内存泄露的情况
(1)堆内存泄漏 (Heap leak)。对内存指的是程序运行中根据需要分配通过malloc,realloc new等从堆中分配的一块内存,再是完成后必须通过调用对应的 free或者delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak.

(2)系统资源泄露(Resource Leak).主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。

⑴ 使用工具软件BoundsChecker,BoundsChecker是一个运行时错误检测工具,它主要定位程序运行时期发生的各种错误。

⑵ 调试运行DEBUG版程序,运用以下技术:CRT(C run-time libraries)、运行时函数调用堆栈、内存泄漏时提示的内存分配序号(集成开发环境OUTPUT窗口),综合分析内存泄漏的原因,排除内存泄漏。

避免内存泄露的方法 使用智能指针

shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候,内存才会被释放。

1.不要用一个原始指针初始化多个shared_ptr。
2.不要再函数实参中创建shared_ptr,在调用函数之前先定义以及初始化它。
3.不要将this指针作为shared_ptr返回出来。
4.要避免循环引用。

(1)shared_ptr共享的智能指针:

shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候,内存才会被释放。

(2)unique_ptr独占的智能指针:

<1>Unique_ptr是一个独占的智能指针,他不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另外一个 unique_ptr。

<2>unique_ptr不允许复制,但可以通过函数返回给其他的unique_ptr,还可以通过std::move来转移到其他的unique_ptr,这样它本身就不再 拥有原来指针的所有权了。

(3)weak_ptr弱引用的智能指针:
弱引用的智能指针weak_ptr是用来监视shared_ptr的,不会使引用计数加一,它不管理shared_ptr内部的指针,主要是为了监视shared_ptr的生命 周期,更像是shared_ptr的一个助手。 weak_ptr没有重载运算符*和->,因为它不共享指针,不能操作资源,主要是为了通过shared_ptr获得资源的监测权,它的构造不会增加引用计数,它的析构不会减少引用计数,纯粹只是作为一个旁观者来监视shared_ptr中关连的资源是否存在。 weak_ptr还可以用来返回this指针和解决循环引用的问题。

c++11

关键字及新语法

(1)auto关键字及用法
  auto并没有让C++成为弱类型语言,也没有弱化变量什么,只是使用auto的时候,编译器根据上下文情况,确定auto变量的真正类型。

(2)nullptr关键字及用法

NULL 编译器解析为0 nullptr为空指针

void func(int index);
void func(int * index)

func(NULL)
func(nullptr)

(3)for循环语法

for each

STL容器

(1)std::array

数组

(2)std::forward_list

单向链表 list是双向链表

(3)std::unordered_map

哈希表

(4)std::unordered_set

多线程

(1)std::thread

(2)st::atomic
支持原子数据类型 自己加锁

(3) std::condition_variable

智能指针内存管理

(1)std::shared_ptr 引用计数

(2)std::weak_ptr 解决智能指针相互引用

其他

(1)std::function、std::bind封装可执行对象

函数指针

(2)lamda表达式