本文记录一些c++的笔记,缓慢更新中。

目录

1. windows数据类型 (2017-05-08)

2. 监视某个exe是否在运行 (2017-05-01)

3. 启动外部程序 (2017-05-08)

4. Windows下的简易socket程序 (2017-05-24)

5. STL部分源码笔记(TIME NaN)

end. Tips(TIME NaN)


windows数据类型

在Windows编程/MFC中,常常遇见一些基础类型都是大写字母,而且与c/c++中的基础类型不一致的情况, 其实这些类型仍然是c/c++中的基础类型,只是微软为了解决兼容问题,自己所定义的一些类型,下表(移动端不可见,请使用PC浏览器查看)给出了windows中常见的基础类型。

类型名称 定义 类型名称 定义
BOOL typedef int BOOL PBOOL typedef BOOL *PBOOL
BOOLEAN typedef BYTE BOOLEAN PBOOLEAN typedef BOOLEAN *PBOOLEAN
BYTE typedef unsigned char BYTE PBYTE typedef BYTE *PBYTE
CHAR typedef char CHAR PCHAR typedef CHAR *PCHAR
UCHAR typedef unsigned char UCHAR PUCHAR typedef UCHAR *PUCHAR
SHORT typedef short SHORT PSHORT typedef SHORT *PSHORT
WORD typedef unsigned short WORD PUSHORT typedef USHORT *PUSHORT
DWORD typedef unsigned long DWORD PWORD typedef WORD *PWORD
DWORD32 typedef unsigned int DWORD32 PDWORD typedef DWORD *PDWORD
DWORD64 typedef unsigned _int64 DOWRD64 PDWORD32 typedef DOWORD32 *PDWORD32
INT typedef int INT PDWORD64 typedef DWORD64 *PDWORD64
INT32 typedef signed int INT32 PINT typedef INT *PINT
INT64 typedef signed _int64 INT64 PINT32 typedef INT32 *PNT32
UINT typedef unsigned int UINT PINT64 typedef INT64 *PINT64
UINT32 typedef unsigned int UINT32 PUINT typedef UINT *PUINT
UINT64 typedef unsigned _int64 UINT64 PUINT32 typedef UINT32 *PUINT32
LONG typedef long LONG PUINT64 typedef UINT64 *PUINT64
LONG32 typedef signed int LONG32 PLONG typedef LONG *PLONG
LONG64 typedef _int64 LONG64 PLONG32 typedef LONG32 *PLONG32
DOWRDLONG typedef ULONGLONG DWORDLONG PLONG64 typedef LOGN64 *PLONG64
LONGLONG typedef _int64 LONGLONG PDWORDLONG typedef DWORDLONG *PDWORDLONG
ULONG typedef unsigned long ULONG PLONGLONG typedef LONGLONG *PLONGLONG
ULONG32 typedef unsigned int ULONG32 PULONG typedef ULONG *PULONG
ULONGLONG typedef unsigned _int64 ULONGLONG PULONG32 typedef ULONG32 *PULONG32
ULONG64 typedef unsigned _int64 ULONG64 PULONGLONG typedef ULONGLONG *PULONGLONG
FLOAT typedef float FLOAT PULONG64 typedef ULONG64 *PULONG64
PFLOAT typedef FLOAT *PFLOAT LPBOOL typedef BOOL far *LPBOOL
LPWORD typedef WORD *LPWORD LPDWORD typedef DWORD *LPDWORD
LPINT typedef int *LPINT LPLONG typedef long *LPLON
PWCHAR typedef CHAR *PWCHAR PWSTR typedef __nullterminated WCHAR *PSTR
PCWSTR typedef __nullterminated CONST WCHAR *PCSTR LPWSTR typedef __nullterminated WCHAR *LPSTR

该表未包含所有windows数据类型,具体可参考 windows数据类型 - mouseOS 技术小站 [链接已失效]


监视某个exe是否在运行

在解决这个问题之前,先得弄清楚c++中是否有一个数据结构来保存系统中运行的进程,查阅相关api知在c++中有一个名为PROCESSENTRE32的结构体,用于存放进程快照信息,其结构体源码如下:

typedef struct tagPROCESSENTRY32
{
    DWORD dwSize; //结构大小
    DWORD cntUsage; //引用计数,已弃用
    DWORD th32ProcessID;  //pid
    ULONG_PTR th32DefaultHeapID;  //默认进程堆,已弃用
    DWORD th32ModuleID; //进程模块,已弃用
    DWORD cntThreads; //此进程开启的进程计数器
    DWORD th32ParentProcessID;  //父进程pid
    LONG pcPriClassBase;  //线程优先级
    DWORD dwFlags;  //已弃用
    TCHAR szExeFile[MAX_PATH];  //进程全名(\*.exe)
} PROCESSENTRY32, *PPROCESSENTRY32;

得知这一结构体的存在后,需要找到如何去获取这样的一个结构体数组或者其他包含这个结构体的数据结构,在c++的库函数中,存在一个CreateToolhelp32Snapshot函数,这个函数的原型是HANDLE WINAPI CreateToolhelp32Snapshot(DWORD dwFlags, DWORD th32ProcessID),头文件为TlHelp32.h,其中的参数含义如下:
dwFlags:指定快照中包含的系统内容,可以使用下列数值的一个或多个:

TH32CS_INHERIT - 声明快照句柄是可继承的。
TH32CS_SNAPALL - 在快照中包含系统中所有的进程和线程。
TH32CS_SNAPHEAPLIST - 在快照中包含在th32ProcessID中指定的进程的所有的堆。
TH32CS_SNAPMODULE - 在快照中包含在th32ProcessID中指定的进程的所有的模块。
TH32CS_SNAPPROCESS - 在快照中包含系统中所有的进程。
TH32CS_SNAPTHREAD - 在快照中包含系统中所有的线程。
Const TH32CS_SNAPHEAPLIST = &H1
Const TH32CS_SNAPPROCESS = &H2
Const TH32CS_SNAPTHREAD = &H4
Const TH32CS_SNAPMODULE = &H8
Const TH32CS_SNAPALL = (TH32CS_SNAPHEAPLIST | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE)
Const TH32CS_INHERIT = &H80000000

th32ProcessID:指定将要快照的进程ID,参数为0表示当前进程。注意该进程只有在设置了TH32CS_SNAPHEAPLIST或者TH32CS_SNAPMODULE后才会生效。 返回值为快照的句柄,如果调用失败,返回INVALID_HANDLE_VALUE

通过查阅到该函数,便可以开始编写代码实现监视进程的功能了,这个功能的基本思路是遍历系统中 已经运行的进程列表,如果在该列表中能找到需要监视的进程,就可以说明该进程在运行,反之则说明没有运行该exe。

代码如下:

bool hasProcessRunning(string exename)
{
  map<string, int> processMap;
  PROCESSENTRY32 pe32;
  pe32.dwSize = sizeof(pe32);

  HANDLE processSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if (processSnap == INVALID_HANDLE_VALUE)
  {
     cout<<"CreateToolhelp32Snapshot Error!"<<endl;
     return false;
  }
  bool result = Process32First(processSnap, &pe32);
  int num(0);

  while (result)
  {
    string name = pe32.szExeFile;
    int id = pe32.th32ProcessID;

    processMap.insert(pair<string, int>(name, id));
    result = Process32Next(processSnap, &pe32);
  }

  CloseHandle(processSnap);

  map<string, int>::iterator it = processMap.begin();
  for (; it != processMap.end(); ++it)
    if (it->first == exename) return true;

  return false;
}

测试:
首先编写简单的c++与c#测试程序(或者使用其他测试可运行文件),该程序为控制台程序,效果为在控制台输出一行“xxx is running”。 然后开启监视程序,分别传入测试程序的名称,查看输出结果,然后关闭测试程序再次在监视程序中查找测试程序,查看输出结果。

// CppTest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <conio.h>

int main()
{
	puts("CppTest.exe is running...");
	puts("press any key to exit...");
	while (true)
	{
		if (_getch())
			break;
	}
	return 0;
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CSharpTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("CSharpTest.exe is running...");
            Console.WriteLine("press any key to exit...");

            Console.ReadKey(true);
        }
    }
}

运行结果如下:


启动外部程序

在c++程序中,有时候一个程序需要另外一个程序的配合才能正常完成操作,这时候会有一个启动外部程序的过程。
这一过程的实现并不复杂,在win32API中,存在一个函数可以实现这一功能,它就是CreateProcess,该函数的头文件为windows.h,函数原型较为复杂,如下:

BOOL CreateProcess
(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATIONlpProcessInformation
);

其中各个参数含义如下:

lpApplicationName  
指向一个NULL结尾的、用来指定可执行模块的字符串。  
这个字符串可以是可执行模块的绝对路径,也可以是相对路径,在后一种情况下,函数使用当前驱动器和目录建立可执行模块的路径。  
这个参数可以被设为NULL,在这种情况下,可执行模块的名字必须处于 lpCommandLine 参数最前面并由空格符与后面的字符分开。  

lpCommandLine  
指向一个以NULL结尾的字符串,该字符串指定要执行的命令行。  
这个参数可以为空,那么函数将使用lpApplicationName参数指定的字符串当做要运行的程序的命令行。  
如果lpApplicationName和lpCommandLine参数都不为空,那么lpApplicationName参数指定将要被运行的模块,lpCommandLine参数指定将被运行的模块的命令行。新运行的进程可以使用GetCommandLine函数获得整个命令行。C语言程序可以使用argc和argv参数。  

lpProcessAttributes  
指向一个SECURITY_ATTRIBUTES结构体,这个结构体决定是否返回的句柄可以被子进程继承。如果lpProcessAttributes参数为空(NULL),那么句柄不能被继承。  
在Windows NT中:SECURITY_ATTRIBUTES结构的lpSecurityDescriptor成员指定了新进程的安全描述符,如果参数为空,新进程使用默认的安全描述符。  

lpThreadAttributes  
同lpProcessAttribute,不过这个参数决定的是线程是否被继承.通常置为NULL.  
bInheritHandles
指示新进程是否从调用进程处继承了句柄。  
如果参数的值为真,调用进程中的每一个可继承的打开句柄都将被子进程继承。被继承的句柄与原进程拥有完全相同的值和访问权限。  

dwCreationFlags  
指定附加的、用来控制优先类和进程的创建的标志。以下的创建标志可以以除下面列出的方式外的任何方式组合后指定。  

lpEnvironment  
指向一个新进程的环境块。如果此参数为空,新进程使用调用进程的环境。  
一个环境块存在于一个由以NULL结尾的字符串组成的块中,这个块也是以NULL结尾的。每个字符串都是name=value的形式。  
因为相等标志被当做分隔符,所以它不能被环境变量当做变量名。  
与其使用应用程序提供的环境块,不如直接把这个参数设为空,系统驱动器上的当前目录信息不会被自动传递给新创建的进程。对于这个情况的探讨和如何处理,请参见注释一节。  
环境块可以包含Unicode或ANSI字符。如果lpEnvironment指向的环境块包含Unicode字符,那么dwCreationFlags字段的CREATE_UNICODE_ENⅥRONMENT标志将被设置。如果块包含ANSI字符,该标志将被清空。  
请注意一个ANSI环境块是由两个零字节结束的:一个是字符串的结尾,另一个用来结束这个快。一个Unicode环境块是由四个零字节结束的:两个代表字符串结束,另两个用来结束块。  

lpCurrentDirectory  
指向一个以NULL结尾的字符串,这个字符串用来指定子进程的工作路径。这个字符串必须是一个包含驱动器名的绝对路径。如果这个参数为空,新进程将使用与调用进程相同的驱动器和目录。这个选项是一个需要启动应用程序并指定它们的驱动器和工作目录的外壳程序的主要条件。  

lpStartupInfo  
指向一个用于决定新进程的主窗体如何显示的STARTUPINFO结构体。  

lpProcessInformation  
指向一个用来接收新进程的识别信息的PROCESS_INFORMATION结构体。

测试:
使用该函数打开一个外部程序,参数为该程序完整路径的代码如下:

BOOL processStart(string path)
{
  STARTUPINFO si;
  memset(&si,0,sizeof(STARTUPINFO));
  si.cb=sizeof(STARTUPINFO);
  si.dwFlags=STARTF_USESHOWWINDOW;
  si.wShowWindow=SW_SHOW;
  PROCESS_INFORMATION pi;

  return CreateProcess(TEXT(path),
                      NULL,
                      NULL,
                      NULL,
                      FALSE,
                      0,
                      NULL,
                      NULL,
                      &si,
                      &pi
                      );

Windows下的简易socket程序

在这个例子中使用的IDE是 DevCpp 4.9.2版本。

注意:在Dev中使用socket API需要在 “Project -> Project Options -> Parameters -> Linker”中添加一行”-lwsock32”,然后在预编译语句下方添加”#pragma comment(lib,”WS2_32”)”才可以正常使用socket相关API。

在c++程序中对TCP与UDP的操作几乎是通过socket来完成的。 socket通信流程图如下:

图片来自:socket 的通信过程_CSDN

Windows下的Socket API在使用之间需要通过WSAStartup函数进行初始化来指明WinSocket规范的版本,其函数原型为

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);

WSAStartup函数执行成功后,会将与 ws2_32.dll 有关的信息写入 WSAData 结构体变量。WSAData 的定义如下:

typedef struct WSAData {
    WORD           wVersion;  //ws2_32.dll 建议我们使用的版本号
    WORD           wHighVersion;  //ws2_32.dll 支持的最高版本号
    //一个以 null 结尾的字符串,用来说明 ws2_32.dll 的实现以及厂商信息
    char           szDescription[WSADESCRIPTION_LEN+1];
    //一个以 null 结尾的字符串,用来说明 ws2_32.dll 的状态以及配置信息
    char           szSystemStatus[WSASYS_STATUS_LEN+1];
    unsigned short iMaxSockets;  //2.0以后不再使用
    unsigned short iMaxUdpDg;  //2.0以后不再使用
    char FAR       *lpVendorInfo;  //2.0以后不再使用
} WSADATA, *LPWSADATA;

知道了以上内容,可以参考下面的代码来对socket编程有更深一步的了解。

//服务端代码
#include <stdio.h>
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib")  //加载 ws2_32.dll
int main()
{
    //初始化 DLL
    WSADATA wsaData;
    WSAStartup( MAKEWORD(2, 2), &wsaData);
    //创建套接字
    SOCKET servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    //绑定套接字
    sockaddr_in sockAddr;
    memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充
    sockAddr.sin_family = PF_INET;  //使用IPv4地址
    sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具体的IP地址
    sockAddr.sin_port = htons(1234);  //端口
    bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
    //进入监听状态
    listen(servSock, 20);
    //接收客户端请求
    SOCKADDR clntAddr;
    int nSize = sizeof(SOCKADDR);
    SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);
    //向客户端发送数据
    char *str = "Hello World!";
    send(clntSock, str, strlen(str)+sizeof(char), NULL);
    //关闭套接字
    closesocket(clntSock);
    closesocket(servSock);
    //终止 DLL 的使用
    WSACleanup();
    return 0;
}
//客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")  //加载 ws2_32.dll
int main()
{
    //初始化DLL
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    //创建套接字
    SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    //向服务器发起请求
    sockaddr_in sockAddr;
    memset(&sockAddr, 0, sizeof(sockAddr));  //每个字节都用0填充
    sockAddr.sin_family = PF_INET;
    sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    sockAddr.sin_port = htons(1234);
    connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
    //接收服务器传回的数据
    char szBuffer[MAXBYTE] = {0};
    recv(sock, szBuffer, MAXBYTE, NULL);
    //输出接收到的数据
    printf("Message form server: %s\n", szBuffer);
    //关闭套接字
    closesocket(sock);
    //终止使用 DLL
    WSACleanup();
    system("pause");
    return 0;
}

参考资料:C/C++ socket编程教程:1天玩转socket通信技术_C语言中文网


STL部分源码笔记

list

本质:双向环状链表

vector

本质:动态自动扩充数组

template <class T, class Alloc = alloc>
class vector
{
public:
    typedef T value_type;
    typedef value_type* iterator;
    typedef value_type& reference;
    typedef size_t size_type;
protected:
    iterator start;
    iterator finish;
    iterator end_of_storage;
public:
    iterator begin() { return start; }
    iterator end() { return finish; }
    size_type size() const { return size_type(end()-begin()); }
    size_type capacity() const
    { return size_type(end_of_storage-begin()); }
    bool empty() const { return begin() == end(); }
    reference operator[] (size_type n)
    { return *(begin()+n); }
    reference front() { return *begin(); }
    reference back() { return *(end()-1); }
};

Tips

Guard头文件 (防御式声明)

#ifndef __HEADERNAME__
#define __HEADERNAME__
...
#endif

作用:防止重复include

const用法

const 除了表示常量之外,在函数中也可以使用,const出现在函数中时,有三种可能的位置。

//用于修饰返回值的时候
//如果返回值为指针形式,该返回值(指针)不可被修改,且只能赋值给const修饰的同类型指针
//返回值为值类型,加const修饰没有任何价值,因为值类型传递返回的时候会返回一个副本
const char* fun() { return value; }

//以下语句非法
char* str = fun();
//以下语句合法
const char* str = fun();

//不会改变其返回值的时候加上const
//类比于c#中的get属性
string fun() const { return value; }

//参数值不可在函数体中被修改
string fun(const string v) { value = v; return value; }

用于修饰指针的时候,也有三种情况。

//左定值,const出现在*左边,指向的值不可改变
const int *p = 8;
//右定向,const出现在*右边
//其指向的地址不可更改,但地址内值可以更改
int num = 8;
int* const p = &num;
//指向的地址与值不可更改
int a = 8;
const int* const p = &a;

friend - 友元

class person
{
private:
    string name;
public:
    //友元函数
    friend void print_name(const person& p);
    //友元类
    friend class dog;
}

void print_name(const person& p)
{ out << "name:" << p.name; }

temp object - 临时对象

用法: typename();

operator - 操作符重载

用法: 返回值 operator 操作符 (params) { }
« 操作符不能作为成员函数重载,重载格式为:

//以下代码非法
ostream operator << const (ostream& os, const person& p) { return os << p.name; }
//以下代码合法
ostream& operator << (ostream& os, const person& p) { return os << p.name; }

如果考虑连续性操作符的情况,操作符重载返回值不能为void。

拷贝构造与拷贝赋值(深浅拷贝)

类中如果带有指针,需要手动指定拷贝构造和拷贝赋值。

//Big three
class person
{
public:
{
    //one
    person(const char* name);
    //one - 拷贝构造
    person(const person& other);
    //two - 拷贝赋值
    person& operator=(const person& other);
    //three - 析构
    ~person();
private:
    char* name;
};

inline person& person::operator=(const person& other)
{
    //检测自我赋值(self assignment)
    if (this == &other)
        return *this;
    
    //先清理原先内存
    delete[] name;
    //申请新内存
    name = new char[strlen(other.name)];
    //深拷贝
    strcpy(name, other.name);
    return *this;
    
}

struct 和 class

heap 和 stack

new 和 delete

person *stu = new person("Zhang san");

//编译器优化代码如下:
void* mem = operator new (sizeof(person));  //分配内存
stu = static_cast<person*>(mem);            //类型转化
stu->person::person("Zhang san");           //调用构造函数
delete stu;

//编译器优化代码如下:
person::~person(stu);   //调用析构函数
operator delete(stu);   //释放内存

泛化、特化、偏特化

STL源码样例

#define __STL_TEMPLATE_NULL template<>

//泛化
template <class Key>
struct hash { }; 
//特化 (全特化)
__STL_TEMPLATE_NULL struct hash<char>
{ size_t operator()(char x) const { return x; } };

//泛化
template <class T, class Alloc = alloc>
class vector
{
    ...
};
//偏特化
template <class Alloc>
class vector<bool, Alloc>
{
    ...
};

//泛化
template <class Iterator>
struct iterator_traits
{
    ...
};
//偏特化(指针)
template <class T>
class iterator_traites<T*>
{
    ...
};

虚函数 - virtual

class shape
{
public:
    //纯虚函数
    virtual void draw() const = 0;
    //虚函数
    virtual void error(const std::string& msg);
    //非虚函数
    int object_id() const;
    ...
}

智能指针

使用class去模拟指针的操作,本质上是一个class,却拥有所有指针的操作(pointer-like object)。

//重载++时,带参数的为i++,不带参数的为++i,但是该参数并无作用,仅仅用于区分
//STL中list_iterator源码
template <class T, class Ref, class Ptr>
struct __list_iterator
{
    typedef __list_iterator<T, Ref, Ptr> self;
    typedef bidirection_iterator_tag iterator_category;
    typedef T value_type;
    typedef Ptr pointer;
    typedef Ref reference;
    typedef __list_node<T>* link_type;
    typedef ptrdiff_t difference_type;

    link_type node;

    reference operator*() const { return (*node).data; }
    pointer operator->() const { return &(operator*()); }
    //++iter
    self& operator++()
    {
        node = (link_type)((*node).next);
        return *this;
    }
    //iter++
    self operator(int)
    {
        //此处不会调用重载的*操作
        //因为先遇到了=操作符,会调用copy ctor用于创建tmp并且以*this为初值
        //故*this将被解释为ctor的参数
        //copy ctor:
        //__list_iterator(const interator& x) : node(x.node) { }
        self tmp = *this;
        ++*this;
        return tmp;
    }
    ...
};

其中,iter++和++iter的返回值不同的原因是为了符合指针的后++不允许连续两次的操作

list<int> c;
auto iter = c.begin();
++++iter; //合法
iter++++; //非法

4种类型转换函数

  1. const_cast
//常量转化为非常量
//一般用于修改指针
int array[4] = { 1, 2, 3, 4 };
const int *const_ptr = array;
int *ptr = const_cast<int*>(const_ptr);

const_ptr[1] = 100; //error
ptr[1] = 100;  //pass
  1. static_cast
//与c中的强制转换基本一致,不对类型进行检查,转换存在安全隐患
//一般用于基类和派生类的转换、基础类型之间的转换
//static_cast不能转换掉原有的const、volatile之类的属性
//c++中的隐式转换就是通过static_cast实现的
float pi = 3.14159f;
int pi_int = static_cast<int>(pi);  //pi_int = 3;

class base {};
class sub : public base {};

base b;
sub s;
base *b_ptr = static_cast<base*>(s);  //pass & safe
sub *s_ptr = static_cast<sub*>(b);  //pass & unsafe
  1. dynamic_cast
//强制类型转换的手段
//在运行时进行类型检查,不能用于基础类型转换
//基类中没有虚函数,使用dynamic_cast编译不过
//在类的转换时,在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的。
//在进行下行转换 时,dynamic_cast具有类型检查的功能,比 static_cast更安全。
//向上转换即为指向子类对象的向下转换,即将父类指针转化子类指针。
//向下转换的成功与否还与将要转换的类型有关,即要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同,否则转换失败。
class base
{
public:
    base() {}
    virtual ~base() {}
    void print() { cout<<"base print called"<<endl; }
};

class sub : public base
{
public:
    sub() {}
    ~sub() {}

    void print() { cout<<"sub print called"<<endl; }
}

sub *s = new sub();
s->print(); //output: sub print called

base *b = dynamic_cast<base*>(sub);
if (b != nullptr)
    b->print(); //output: base print called

base *b = new base();
b->print(); //output: base print called
sub *s = dynamic_cast<sub*>(base);  //pass but s == nullptr
  1. reinterpret_cast
    强制类型转换符用来处理无关类型转换
    通常为操作数的位模式提供较低层次的重新解释
    仅仅重新解释了给出的对象的比特模型,并没有进行二进制的转换

适用场景:

C++11新特性

Variadic Templates

//用于处理无参数的情况
void print() { }

template <typename T, typename... Types>
void print(const T& obj, const Types&... args)
{
    cout<<obj<<endl;
    print(args...);
}

»编译器优化

在c++11下,vector<vector<int>> a;的写法可以被正确识别了,旧版本写法vector<vector<int> > a;

nullptr & std::nullptr_t

//函数声明
void fun(int);
void fun(void*);

//函数调用
fun(0);         //c++11能够正确识别调用函数fun(int)
fun(NULL);      //调用函数fun(int),因为c++中NULL的定义为 define NULL 0
fun(nullptr);   //调用函数fun(void*)

//c语言中的NULL定义
#define NULL ((void*)0)
//c++中的NULL定义
#define NULL 0
#else
#define NULL ((void *)0)
#endif

auto 关键字

Uniform Initialization - 一致性初始化

int values[] { 1,2,3 };
vector<int> v { 1,2,3,4 };
vector<string> strs { "one", "two", "three" };

//以下两行代码功能相等
complex<double> c { 4.0, 3.0 };
complex<double> c(4.0, 3.0);

initalizer_list

对于c++中的一致性初始化而言,其内部使用的initalizer_list可以用于接受一个{}包围的值列表。

void print(std::initalizer_list<int> vals)
{
    for (auto v:vals)
        cout<<v<<" ";
    cout<<endl;
}

//调用方式
print({1,2,3,4,5});

range-based for statement

//使用以下语法对容器进行遍历
for (decl : coll)
{
    statement
}

//example
for (int i : {1,2,3,4,5,6}) { }

vector<double> vec;
for (auto elem : vec) { cout<<elem<<endl; }

//对于引用,需要使用&
for (auto& elem : vec) { elem*=3; }

=defalut, =delete

class zoo
{
public:
    zoo(int i, int j) : a(i), b(j) { }
    zoo(const zoo&) = delete;
    zoo(zoo&&) = default;
    
    zoo& operator=(const zoo&) = default;
    zoo& operator=(const zoo&&) = delete;
    
    virtual ~zoo() { }
private:
    int a, b;

}

using 新用法

使用using 来对模板进行化名处理。

template <typename T>
using Vec = std::vector<T, MyAlloc<T>>;

//使用Vec代表该模板类型
//使用宏定义和typedef均无法达到相同效果
//但是Vec无法被特化和偏特化
Vec<int> vec;

使用using对类型进行化名处理

//以下代码等同
typedef void(*func)(int, int);
using func = void(*)(int, int);

//调用
void swap(int a, int b) { a^=b^=a^=b; }
func f = swap;

template<typename T>
struct container
{
    //以下代码等同
    typedef T value_type;
    using value_type = T;
}

decltype

decltype关键字与typeof作用一致。

Lambdas

当可选1、2、3有一个存在时,参数列表的()一定要出现,即使没有参数

//无返回值无参数的lambda
[] { std::cout<<"easy lambda"<<endl; }
//直接调用
[] { std::cout<<"easy lambda"<<endl; }();
//间接调用
auto f = [] { cout<<"easy lambda"<<endl; };
f();

//带外部参数的lambda
int id = 0;
//pass by value
//如果没有mutable,id++操作无法执行(编译器报错increment of read-only variable 'id')
auto f = [id]() mutable
{
    cout<<"id:"<<id<<endl;
    id++;
};