一些C++笔记
本文记录一些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 = #
//指向的地址与值不可更改
int a = 8;
const int* const p = &a;
friend - 友元
- 类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员
- 友元函数并不是成员函数
- 相同class的各个objects互为friend,或者说类与自身互为友元类
- 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
- 如果没有标明成员函数或者成员变量的访问权限级别,struct中默认是public,class中默认是private。
- 当class和struct相互继承的时候,权限级别取决于子类的类型。
heap 和 stack
- stack是存在于某作用域(scope)的一块内存空间,函数内声明的任何变量,其所使用的内存均取自于stack。
- heap是由操作系统提供的一块global内存空间,程序可以通过动态分配从中获取。
new 和 delete
- new: 先分配memory,再调用ctor
person *stu = new person("Zhang san");
//编译器优化代码如下:
void* mem = operator new (sizeof(person)); //分配内存
stu = static_cast<person*>(mem); //类型转化
stu->person::person("Zhang san"); //调用构造函数
- delete: 先调用dctor,再回收内存
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种类型转换函数
- 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
- 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
- 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
- reinterpret_cast
强制类型转换符用来处理无关类型转换
通常为操作数的位模式提供较低层次的重新解释
仅仅重新解释了给出的对象的比特模型,并没有进行二进制的转换
适用场景:
- 从指向函数的指针转向另一个不同类型的指向函数的指针
- 从一个指向对象的指针转向另一个不同类型的指向对象的指针
- 指针转向足够大的整数类型
- 从整形或者enum枚举类型转换为指针
C++11新特性
Variadic Templates
- 使用…作为数量不定的模板标识符,可以传入任意个数的参数,类比于csharp中的params。
//用于处理无参数的情况
void print() { }
template <typename T, typename... Types>
void print(const T& obj, const Types&... args)
{
cout<<obj<<endl;
print(args...);
}
- 使用sizeof…(args)来计算参数个数
- 优点:可以很方便完成recursive function call(递归调用)和recursive inheritance(递归继承)
»编译器优化
在c++11下,vector<vector<int>> a;
的写法可以被正确识别了,旧版本写法vector<vector<int> > a;
nullptr & std::nullptr_t
- c++11允许使用nullptr代替0或者NULL用于标识空指针
- 引进nullptr之后就可以彻底弃用NULL了
- nullptr的类型是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 关键字
- With c++11, you can declare a variable or an object without specifying its specific type by using auto.
- Using auto is especially useful where the type is a pretty long and/or complicated expression(like lambda).
- 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<T>
实现,其关联至一个array<T, n>。
调用函数时array内的元素可被编译器分解逐一传给函数。
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中已有ctor的定义,编译器不会给出default ctor,但是如果加上=default,就可以重新获取并使用default ctor。
- =delete意味着该函数已被抛弃,不可使用(编译器会报异常“[Error] use of deleted function ‘function name’”)
- 成员函数不能=default
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 新用法
- Alias Template (template typedef)
使用using 来对模板进行化名处理。
template <typename T>
using Vec = std::vector<T, MyAlloc<T>>;
//使用Vec代表该模板类型
//使用宏定义和typedef均无法达到相同效果
//但是Vec无法被特化和偏特化
Vec<int> vec;
- Type Alias
使用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
- C++11 introduced lambdas, allowing the definition of inline fuctionality, which can be used as a parameter or a local object. Lambdas change the way c++ standard library is used.
- A lambda is a definition of functionality that can be defined inside statements and expressions. Thus, you can use a lambda as an inline function. The minimal lambda function has no parameters and simply does something.
- 语法格式
[外部变量](参数列表)[mutable(可选1)][throw_spec(可选2)][->return_type(可选3)]() { body }
当可选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++;
};
- 更多有关lambda函数的信息参见此链接: C++ 中的 Lambda 表达式