leelongcrazy's blog

C++进阶记录

神奇的用法:

通过编译执行printf("\a");可以让电脑发出声音。Windows如此

#pragma

  • 在所有的预处理指令中,#pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。 #pragma once 只要在头文件的最开始加入这条指令就能够保证头文件被编译一次

#ifndef,#define,#endif这个是C++语言相关,这是C++语言中的宏定义,通过宏定义避免文件多次编译。所以在所有支持C++语言的编译器上都是有效的,如果写的程序要跨平台,最好使用这种方式(被称为预处理指令)

#pragma comment

该指令将一个注释记录放入一个对象文件或可执行文件中。 常用的lib关键字,可以帮我们连入一个库文件。

for(;;) --> 近似于while(True) 死循环停留

include头文件时尖括号<>与双引号""的区别:

  • 系统自带的头文件用尖括号;编译器会在系统文件目录查找;
  • 用户自定义的头文件用双引号导入;编译器会先在用户目录下查找,然后再到C++安装目录中查找;最后在系统文件中查找

GetTickCount()

GetTickCount()是一种函数,返回从操作系统启动所经过的毫秒数,返回值是DWORD。

获取两次,取差值可以用于计时。

try,catch异常捕获

// 异常捕获
try{
    // to do something
    throw "Error";
}
catch (char * error){
    std::cout << error << std::endl;
}
catch(...){ // 这里捕获前面没有捕获到的错误,...代表一切错误
    std::cout << "未知错误" << std::endl;
}

C++编码安全

  • 不使用无长度限制的字符拷贝函数
  • switch中应有default
  • 避免函数的声明和实现不同(注意函数名和参数,及返回值类型)
  • 不得直接使用刚分配的未初始化的内存-》对新分配的内存赋初值

C++ 变量默认初始化

  1. 全局变量编译器会自动赋初值,局部变量需要自己初始化,否则编译器中报错(MSVC2015测试中会报错,其他编译器未知)
  2. 静态变量无论全局还是局部,编译器都会赋初值。

类型转换

// char * -> string
char * data = "Hello world!";
string str = data;
// 方法2
string str(data, data_len);

// string -> char *
string str = "hello world";
char * pstr = (char *) str.data();

// string -> CString
// 方法1
string str1 = "Hello world.";
CString cStr;
cStr.Format(_T("%s"), str1.c_str());
// 方法2 >> 不会打导致乱码显示 (windows系统,MSVC环境下)
string str1 = "Hello world.";
CA2T temp(st1.c_str());
CString cStr = (LPCTSTR)temp;

// CString -> string
CString cstr = _T("Hello world");
string str = CT2A(cstr.GetString());

string字符串大小写转换的办法

std::transform(ss.begin(), ss.end(), ss.begin(), toupper); // 将string 转换为大写
std::transform(ss.begin(), ss.end(), ss.begin(), tolower); // 将string 转换为小写

C++问题实例

UDP数据发送接收

    CInitSock initSock;
    SOCKET SendSocket = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (SendSocket == INVALID_SOCKET) {
        return -1;
    }
    // 设置超时时间
    long secs = 3, u_secs = 0;
    DWORD dw = (secs * 1000) + ((u_secs + 999) / 1000);
    setsockopt(SendSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&dw, sizeof(dw));

    sockaddr_in addr;
    addr.sin_addr.S_un.S_addr = inet_addr("239.0.0.1");
    addr.sin_family = AF_INET;
    addr.sin_port = htons(28888);
    int len = sizeof(addr);

    string temp = "Hello world.";
    char * send_data = (char *)temp.data();
    int send_len = temp.length();

    int ss = sendto(SendSocket, send_data, send_len, 0, (sockaddr*)&addr, len);
    if (ss < 0) {
        std::cout << "send error" << std::endl;
    }

    char recv_data[1024];
    memset(recv_data, 0, 1024);
    while (1)
    {
        int n = recvfrom(SendSocket, recv_data, 1024, 0, (sockaddr*)&addr, &len);
        if (n < 0) {
            std::cout << "receive message error" << std::endl;
            break;
        }
        string recv = recv_data;
        std::cout << "接收到数据:" << recv << std::endl;
    }

获取指定IP的Mac地址

string getRemoteMac(char * localIP, char * remoteIP)
{
    string temp;
    unsigned char *pbHexMac;
    WSADATA wsaData;

    int iRet = WSAStartup(MAKEWORD(2, 1), &wsaData);
    if (iRet != 0)
    {
        std::cout << "Error" << endl;
        return "";
    }

    IPAddr nRemoteAddr = inet_addr(remoteIP);
    IPAddr nLocalAddr = inet_addr(localIP);
    unsigned char macAddress[6];
    ULONG macAddrLen = 6;
    memset(&macAddress, 0xff, sizeof(macAddress));
    auto res = SendARP(nRemoteAddr, nLocalAddr, macAddress, &macAddrLen);
    if ((res == NO_ERROR) && (macAddrLen == 6)) {
        pbHexMac = macAddress;
        temp.Format(_T("%02X:%02X:%02X:%02X:%02X:%02X "), pbHexMac[0], pbHexMac[1],
        pbHexMac[2], pbHexMac[3], pbHexMac[4], pbHexMac[5]);
        return temp;
    }
    else {
        std::cout << "Send arp error" << std::endl;
        return "";
    }
}

需求:将int类型转化为高,低位两个字节存储

int类型默认为4个字节,32位存储; 首先将int类型转换为unsigned short类型,2个字节,16位存储; 然后分别获取高位,低位存储 代码如下:

    BYTE data[50]; 
    memset(data, 0, sizeof(data));
    int num = 1024;
unsigned short temp = (unsigned short)num;
    data[0] = temp & 0xff; // 获取低位
    data[1] = temp >> 8;  // 右移一个字节,获取高位

MSVC中std::string变量字符串很长,显示不全的解决办法

  1. 调试过程中将变量添加到监视
  2. 在变量名称后面增加.c_str(),s8,便可将变量内容完整显示。

MSVC中调试时,变量显示“字符串中的字符无效”的查看办法

  1. 添加变量到监视;
  2. 在变量名称后面增加.c_str(),s8,便可显示变量内的值。

image-20210716105740688

如果发现监视中的变量显示“表达式有副作用,将不会进行计算”,可点击右侧的重置标识即可重新计算,显示结果。

,s8 //将字符串转换成Unicode显示
,数字 //将变量拆分为数组显示,数字是要显示多少位
,x //16进制查看
,hr //查看Windows HRESULT解释
,wm  //Windows消息

如何将变量显示“字符串中的字符无效”转换为可在代码显示的形式?

获取HTTP Get请求返回的HTML文本

C++实现的HTTP get请求返回的文本大概是这个样子:

HTTP/1.1 200 
Content-Type: 
Date: Fri, 16 Jul 2021 01:30:04 GMT
Content-Length: 1814

<html>
    <head>
        <meta charset="utf-8"><style>p{position:relative;left:20px;} a{position:relative;left:20px;} </style></head>
    <body>
        <h1>
            Hello world!
        </h1>
        <script type="text/javascript">function fun0(){console.log(2333);}
        </script>
    </body></html>

实现获取HTML的功能可以通过按\r\n\r\n切割字符串的形式,并返回切割结果的最后一项。

string response_text = request_get_responese
std::vector<string> rest = split2(response_text, "\r\n\r\n");
    string response = rest.back(); // 获取得返回内容关于html的部分

// split 函数
vector<string> split2(const string &str, const string &pattern)
{
    char * strc = new char[strlen(str.c_str()) + 1];
    strcpy(strc, str.c_str());   //string转换成C-string
    vector<string> res;
    char* temp = strtok(strc, pattern.c_str());
    while (temp != NULL)
    {
        res.push_back(string(temp));
        temp = strtok(NULL, pattern.c_str());
    }
    delete[] strc;
    return res;
}

查看变量在内存中的值

在MSVC中 调试-》窗口-》内存-》内存1/2/3/4内存1,2,3,4只是窗口而已,打开多个可以查看多个变量的值

在内存窗口中输入地址:

  • 输入指针变量名
  • 输入&+变量名image-20210817175039857
  • 输入变量名--》会把变量的值当做地址
  • 输入要监视变量的地址(内存地址)

内存存储内容显示方式

  • 单、双字节整数显示
  • 4字节整数显示,8,16,32,64字节显示image-20210817175506406
  • 是否显示符号位

负数在内存中以补码形式存储。

error C2360: “****”的初始化操作由“case”标签跳过

尝试成功的解决办法:case 后面的语句用大括号包裹 {}

C++多线程编程

在MFC中

1.

> CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc, 
>
> LPVOID pParam, 
>
> nPriority=THREAD_PRIORITY_NORMAL, 
>
> UINT nStackSize=0, 
>
> DWORD dwCreateFlags=0,  
>
> LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL); 
>
> PfnThreadProc:指向工作者线程的执行函数的指针,线程函数原型必须声明如下:
>       UINT ExecutingFunction(LPVOID pParam); 
>
> **总共6个参数,第一个线程处理函数,第二个传递参数,第三,线程优先级,
> 第四,分配堆栈大小,第五,代表线程建立后开始执行或是挂起,第六,线程安全属性指针。**
>
> 请注意,ExecutingFunction()应返回一个UINT类型的值,用以指明该函数结束的原因。一般情况下,返回0表明执行成功。   
>
> pParam:传递给线程函数的一个32位参数,执行函数将用某种方式解释该值。
>  它可以是数值,或是指向一个结构的指针,甚至可以被忽略;  
>
> nPriority:线程的优先级。如果为0,则线程与其父线程具有相同的优先级;  
>
> nStackSize:线程为自己分配堆栈的大小,其单位为字节。如果 nStackSize被设为0,则线程的堆栈被设置成与父线程堆栈相同大小;  
>
> dwCreateFlags:如果为0,则线程在创建后立刻开始执行。如果为CREATE_SUSPEND,则线程在创建后立刻被挂起;  
>
> lpSecurityAttrs:线程的安全属性指针,一般为 NULL 

代码示例:

// 创建后立刻开始执行
CWinThread * thread = AfxBeginThread((AFX_THREADPROC)ThreadFunc, (LPVOID)this, 0, 0, 0, NULL);
thread->m_bAutoDelete = TRUE;

2.

> CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass, 
>
> int nPriority=THREAD_PRIORITY_NORMAL, 
>
> UINT nStackSize=0, 
>
> DWORD dwCreateFlags=0, 
>
> LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL); 
>
> pThreadClass 是指向 CWinThread 的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、
> 退出等;其它参数的意义同形式1。使用函数的这个原型生成的线程也有消息机制,在以后的例子中我们将发现同主线程的机制几乎一样。 
>
> 下面我们对CWinThread类的数据成员及常用函数进行简要说明  
>
> m_hThread:当前线程的句柄;  
>
> m_nThreadID:当前线程的ID  
>
> m_pMainWnd:指向应用程序主窗口的指针  
>
> BOOL  CWinThread::CreateThread(**DWORD** dwCreateFlags=0, 
>
> UINT  nStackSize=0, 
>
> LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL); 

引用

Makefile编写

“#”符号注释