易道云C/C++全栈工程师学习笔记

指针和引用(一)

指针

int* a{},b; a是指针,b是变量。

尽量分开写。

获取一个变量的内存地址

取址运算符&

int a = 5000; int* pa = &a;

指针和变量的数据类型必须一致。

读写一个指针指向的空间

间接运算符*

*pa = 500; 代表往a的内存地址里写入500

(*pa)++; 相当于a++;

std::cout<< "内存地址:" << pa << "\na=" << *pa;

指针数组

int* ptrArray[10];

声明10个int类型的指针

int studentId[5]{10001,10002,10003,10004,10005};
int* ptrStudentId[5];

for (int x = 0; x < 5; x++) {
    ptrStudentId[x] = &studentId[x];
    std::cout << "内存地址:" << ptrStudentId[x] << "值" << *ptrStudentId[x]<<"\n";
}

二维数组指针

int studentId1[2][2]
    {
        {10001,10002},
        {10003,10004}
    };
    int* ptrStudentId1[2][2];
    for (int i = 0; i < 2; i++)
    {
        for (int j = 0; j < 2; j++)
        {
            ptrStudentId1[i][j] = &studentId1[i][j];
            std::cout << "内存地址:" << ptrStudentId1[i][j] << "值" << *ptrStudentId1[i][j] << "\n";
        }
    }

数组内存空间也是连续的

内存地址:001CF6F8值10001 内存地址:001CF6FC值10002 内存地址:001CF700值10003 内存地址:001CF704值10004

指针补充

int* ptr{};

char* cptr{};

占用的空间都是8字节十六进制高位在前

因为指针是一种特殊的变量类型,在32位操作系统下,占8字节,在64位系统下,占16字节。

大多数计算机按高位优先顺序存储32位的数,但基于Intel CPU的计算机按低位优先顺序存储32位的数。

计算机存储字节顺序为:左低右高

16进制:左高右低 对于0X12,1是高位,2是地位

unsigned a{ 99901 };
int* ptr{(int*) &a};
*ptr = -1;

char* ctr{};
ctr = (char*)ptr;
*ctr = 'A'; //41 FF FF FF  //0XFFFFFF41
std::cout << a;

指针实验

(*ptr)++

*ptr++ //先显示*ptr的值,然后让ptr++

指针+1的时候,数值的变化是 +1*指针类型的大小

所以int指针+1肯定要指向下一个地址的空间,所以是+4

想强制让指针后移一个字节,可使用指针类型的强制类型转换为char*

p=(int*)((char*)p+1);

那些绕晕人的指针概念

常量指针

const 变量类型*

所谓的常量指针,即这个指针指向的一个常量的内存地址,常量指针中,不能对其指向的内存地址进行改变,但是指针指向的地址可以改变;

const int a{ 1000 };
const int b{ 2500 };
int c{ 3500 };
const int* ptr{ &a };
ptr = &c;
//*ptr = 300;  不可以
std::cout << *ptr;

指针常量

变量类型* const

所谓的指针常量,即这个指针变量是一个常量,一旦初始化就不可以再指向其他内存地址,但是内存地址中的数据可以读写

int a{ 1000 };
int b{ 1000 };
int* const ptr{ &a };
//ptr = &b; 不可以
*ptr = 999999;

指向常量的常量指针

const 变量类型* const

指向常量的常量指针,即这个指针变量是一个常量,一旦初始化就不可以再指向其他内存地址,因为其本身就是一个指向常量的指针,所以其指向的内存区域也不可以修改!

int const a{ 1000 };
int const b{ 1500 };
const int* const ptrA{ &a };
//ptrA = &b;
//*ptrA = 500;

课后小练习

反汇编思考为什么*ptrA和a结果不一样

const int a{ 1000 };
const int b{ 1400 };

int* ptrA{ (int*)&a };
*ptrA = 9500;
std::cout << *ptrA << std::endl;
std::cout << a << std::endl;

指针和引用(二)

指针和数组

imul 有符号乘法,将被乘数与乘数均作为有符号数open in new window

mul 无符号乘法,将被乘数及乘数均作为无符号数。

可以有三个操作数open in new window:imul eax,eax,0Ch

第3操作数是乘数,

第2操作数是被乘数,

运算结果放入第1操作数。

shl eax,1 左移1位

动态内存分配

C语言中的动态内存分配

malloc(size_t _Size)

字节为单位 返回void* 最多4个GB

calloc(size_t _Count,size_t _Size)

分配count*size字节个内存,并且返回内存分配的地址

realloc(void* _Block,size_t _Size)

C++中的动态内存分配

new int;

new int[4];

delete p;

delete []p;

memset

memcpy

引用

int a{ 5250 }; int& la{ a };

指针和引用(三)

智能指针

std::unique_ptr<int> intPtr{new int {5}};

std::unique_ptr<int[]> intPtr{new int[5] {5,4,3,2,1}};

std::unique_ptr<int[]> intPtr {std::make_unique<int[]>(5)};

std::unique_ptr<int> intPtr {std::make_unique<int>(5)};

u.release()u放弃对指针的控制权,返回指针,并将u置空
u.reset()释放u指向的对象,并将u置为空
u.reset(q)释放u原来指向的对象,令u指向这个q

std::move()

std::shared_ptr<int> ptrA{std::make_shared<int>(1)};

std::cout << ptrA.use_count() << std::endl;

std::cout << ptrA.unique() << std::endl; C++17以后用不了了

reset()会将当前共享指针设置成nullptr,同时如果当前智能指针是最后一个拥有该指针的对象,那么将会释放内存。

字符处理

wchar_t wstr[0xFF]{ L"Hello张三" }; for (int i = 0; wstr[i]; i++) { std::cout << std::hex << (short)wstr[i] << std::endl; }

默认是GBK编码,中文占两个字节,英文占一个字节。

wchar_t为宽字节,UniCode编码,unicode为每种语言中的每个字符设定了统一并且唯一的二进制编码

所以要学会设置编码

#include<locale>
setlocale(LC_ALL, "chs");

scanf_s("%s",Name,5); //限制5个字符

wscanf_s(L"%s",wstr,可接受的最大字符值);

setlocale(LC_ALL, "chs");
wchar_t wstr[0xFF];
std::wcout << L"请输入您的名字" << std::endl;
std::wcin >> wstr;
std::wcout << wstr;

strlen()

wcslen()

联合体

基本语法

union USER
{
	short sHP;
	int nHP;
	double fHP;
};

Union用来共享内存,内存大小取决于占内存最大的成员。

union USER
{
	short sHP;
	int nHP;
	double fHP;
};

int main() 
{
	USER user;
	std::cout << sizeof(user)<<std::endl;

	user.nHP = 0;
	user.sHP = -1;
	std::cout << user.sHP << std::endl;
	std::cout << user.nHP << std::endl;

执行user.sHp = -1后,user内存空间

内存分布为

11111111 11111111

为0xFF

即11111111 1111111

即为-1

但是读user.nHp时为4个字节

内存分布为

11111111 11111111 00000000 00000000

为0X00FF

即00000000 00000000 11111111 1111111

即为65535

匿名union

union
{
	short sHP;
	int nHP;
	double fHP;
} ls;

缺点就是只能用一次

结构体也可以这样用

struct
{
	int xp;
} ls;

字符串

string

std::string是C++提供的字符串。

std::string str{"哈哈哈"}

std::string 变量名称{"字符串",要截取的长度}

std::string 变量名称{"字符串",起始位置,要截取的长度}

std::string 变量名称(要复制的个数,'字符');

std::to_string();支持各种类型

还有 substring length append 等等操作

str.c_str()和str.data()一模一样 返回一个 const char*

但是在C++17标准后,str.data()返回的是char*

C字符串char*以0结尾,而string有专门记录其长度的属性,在实现的时候,没有严格要求以0结尾。

但是在C++11后,string也要求以0结尾

函数

指针可以传入nullptr空指针,而引用不可以

Role* &beAct 指针类型的引用

main函数的另一面:

argcount 参数个数

c_arg[] 程序的路径及文件名

不定量参数

#include<iostream>
#include<cstdarg>


int Average(int count, ...) 
{
	//va_list arg;
	char* arg;
	va_start(arg, count);
	int sum{};

	for (int i = 0; i < count; i++)
	{
		sum += va_arg(arg, int);
	}
	sum /= count;

	va_end(arg);
	return sum;
}

int main() 
{
	int x = Average(5, 53, 2, 321, 3, 321);
	std::cout << "平均数:" << x;
}

函数是一个内存地址

函数功能是数据

函数指针:

int (*pAdd) (int,int) = Add

类型转换

char (*pAdd)(int,int) = (char (*)(int,int))Add;

声明一个函数指针

typedef char(*pfAdd)(int,int);

using pfAdd = char(*)(int,int);

函数模板

#include<iostream>

template <typename type1>
type1 ave(type1 a, type1 b, type1 c)
{
	return (a + b + c) / 3;
}

int main() {
	std::cout << ave(12.0f, 250.5f, 36.395f) << std::endl;
	std::cout << ave(11,12,13) << std::endl;
	std::cout << ave((char)11,(char)12,(char)13) << std::endl;
    
    
    std::cout << ave<int>(12.0f,250.f,36.395f) <<std::endl;

}

编译器

OneDefinition Rule

#define

#define SHOW(X,Y) void X##Y(){std::cout<<#X;}

SHOW(test, 321321)

test321321();

namespace

全局命名空间 ::p;

assert

#define NDEBUG

可以在当前转换单元关闭assert

static_assert();

控制台API

Windows编程讲什么

API:Application Programming Interface 应用程序编程接口

Windows32API官网:

https://learn.microsoft.com/zh-cn/windows/win32/apiindex/windows-api-list

创建游戏窗口

image-20230512044921377

SMALL_RECT mRect{ 0,0,40-1,20-1 };
if (SetConsoleWindowInfo(hdlOut, FALSE, &mRect)) printf("设置窗口大小成功\n");

FALSE就是窗口相对于缓冲区的上下左右

而TRUE是绝对值,全部基于左上角来计算

if (SetConsoleScreenBufferSize(hdlOut, cRd)) printf("缓冲区设置成功");

设置缓冲区大小

光标与字体

image-20230512170100198

由于基础部分大多本人都熟悉,笔记就没有认真去详细记录,后面只记录和总结较为复杂的知识点,基础部分直接跳过。

Windows编程-线程

多线程开发中经常面临线程同步的问题,这时候就需要对公有操作部分进行上锁处理,windows中的加锁方案实在是太多太杂了,特此专门写一篇以便区分。

互斥量

HANDLE CreateMutex( LPSECURITY_ATTRIBUTESlpMutexAttributes, // 指向安全属性的指针 BOOLbInitialOwner, // 初始化互斥对象的所有者 LPCTSTRlpName // 指向互斥对象名的指针 );

HANDLE hMutex = CreateMutex(NULL,TRUE,NULL);
当前线程调用该互斥对象次数为1,递归计数器设置为1

WaitForSingleObject(hMutex,INFINITE);
当前线程调用该互斥对象次数为2,递归计数器+1,为2

ReleaseMutex(hMutex);//释放占用互斥对象的线程的拥有权,递归计数器-1,递归计数器值为1
ReleaseMutex(hMutex);//释放占用互斥对象的线程的拥有权,递归计数器-1,递归计数器值为0

//递归计数器值为0,互斥对象处于触发/有信号/已通知状态,可以在此为其他线程所占用

lpMutexAttributes SECURITY_ATTRIBUTES,指定一个SECURITY_ATTRIBUTES结构,或传递零值(将参数声明为ByVal As Long,并传递零值),表示使用不允许继承的默认描述符 。

bInitialOwner Long,初始化互斥对象的所有者。TRUE:占有,FALSE:不占有。 如希望立即占有该互斥量,则设为TRUE。 操作系统记录线程ID,将递归计数器设置为1,互斥量处于未触发/无信号/未通知状态。一个互斥体同时只能由一个线程拥有 。 为FALSE,则线程ID为NULL,操作系统将递归计数器设置为0,互斥量处于触发/有信号/已通知状态,不为任何线程所占用 。

lpName String,指向互斥对象名的指针,设置互斥对象的名字。创建一个未命名的互斥体对象。如已经存在这个名字,则打开已命名互斥体 。有了名字就可以跨进程得到同一把锁。

事件对象

HANDLE CreateEvent(   LPSECURITY_ATTRIBUTES lpEventAttributes,// 安全属性   BOOL bManualReset,// 复位方式   BOOL bInitialState,// 初始状态   LPCTSTR lpName // 对象名称 );

HANDLE hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);//创建对象,手动复位,初始化时无信号
WaitForSingleObject(hEvent,INFINITE);
setEvent(hEvent); //把事件设置为有信号状态
ResetEvent(hEvent); //把事件设置为无信号状态

信号量对象

  • 信号量是操作系统提供的一种协调共享资源访问的方法。信号量则由操作系统进行管理,地位高于进程,操作系统保证信号量的原子性。
  • 信号量(英语:semaphore)又称为信号标或者信号灯,是一个同步对象,用于保持在0至指定最大值之间的一个计数值。当线程完成一次对该semaphore对象的等待(wait)时,该计数值减一;当线程完成一次对semaphore对象的释放(release)时,计数值加一。当计数值为0,则线程等待该semaphore对象不再能成功直至该semaphore对象变成signaled状态。semaphore对象的计数值大于0,为signaled状态;计数值等于0,为nonsignaled状态。

HANDLE WINAPI CreateSemaphore(

In_opt LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,//指向SECURITY_ATTRIBUTES的指针;

In LONG lInitialCount, //信号量对象的初始值;

In LONG lMaximumCount, //信号量对象的最大值,这个值必须大于0;

In_opt LPCTSTR lpName //信号量对象的名称;

);

HANDLE g_hSemaphore = CreateSemaphore(NULL          //信号量的安全特性
                                  , 1            //设置信号量的初始计数。可设置零到最大值之间的一个值
                                  , 1            //设置信号量的最大计数
                                  , NULL         //指定信号量对象的名称
                                  );
WaitForSingleObject(g_hSemaphore, INFINITE);      //信号量值-1
ReleaseSemaphore(g_hSemaphore, 1, NULL);          //信号量值+1

我们以windows api的接口为例,讲解下信号量是如何在进程A和进程B间做到进程间同步的。

1、进程A过程 1.1、CreateSemaphore():创建一个名字为Semaphore的信号量,该信号量初始可使用的资源数为0。即S=0. 1.2、WaitForSingleObject():等待信号量>0,就是等待信号量的资源数大于0时。成功后就是S–。(启动进程A后,此处会一直等待,因为创建的信号量初始的值=0,直到进程B打开进程A的信号量,并且释放一个可以使用的资源时,S变成1,才可以继续,进行后面的程序) 1.3、在屏幕打印文字。 1.4、ReleaseSemaphore():释放上面wait成功时占用的1个资源数。执行成功后就是S++。 1.5、等待5s。 2、进程B过程 2.1、OpenSemaphore():打开进程A创建的信号量,名字为Semaphore 2.2、ReleaseSemaphore():递增信号量的当前资源计数,就是S++。S=1 2.3、WaitForSingleObject():等待信号量>0,就是等待信号量的资源数大于0时。成功后就是S–。 2.4、在屏幕打印文字。 2.5、ReleaseSemaphore():释放上面wait成功时占用的1个资源数。成功后就是S++。 2.6、等待5s。

关键代码段

很多人对CRITICAL_SECTION的理解是错误的,认为CRITICAL_SECTION是锁定了资源,其实,CRITICAL_SECTION是不能够“锁定”资源的,它能够完成的功能,是同步不同线程的代码段。简单说,当一个线程执行了EnterCritialSection之后,cs里面的信息便被修改,以指明哪一个线程占用了它。而此时,并没有任何资源被“锁定”。不管什么资源,其它线程都还是可以访问的(当然,执行的结果可能是错误的)。只不过,在这个线程尚未执行LeaveCriticalSection之前,其它线程碰到EnterCritialSection语句的话,就会处于等待状态,相当于线程被挂起了。 这种情况下,就起到了保护共享资源的作用。

CRITICAL_SECTION cs;
EnterCriticalSection(&cs);
...
// 操作dwTime
...
LeaveCriticalSection(&cs);

Windows编程-进程

剪切板通信

存储数据到剪切板

OpenClipboard();

EmptyClipboard();

char* szSendBuf;

HANDLE hClip = GlobalAlloc(GMEM_MOVEABLE,strSend.GetLength()+1);

szSendBuf = (char*)GlobalLock(hClip);

strcpy(szSendBuf,"复制的文字");

GlobalUnLock(hClip);

SetClipboardData(CF_TEXT,hClip);

CloseClipboard(); 

从剪切板中读取数据

OpenClipboard();

IsClipboardFormatAvailable(CF_TEXT);

char* szRecvBuf;

HANDLE hClip = GetClipboardData(CF_TEXT);

szRecvBuf = (char*)GlobalLock(hClip);

GlobalUnlock(hClip);

邮槽通信

接收端

LPCTSTR szSlotName = TEXT("\\\\.\\mailslot\\Mymailslot");
HANDLE hSlot = CreateMailslot(szSlotName,0,MAILSLOT_WAIT_FOREVER,NULL);
if(hSlot == INVALID_HANDLE_VALUE)
{
    TRACE("CreateMailslot failed with %d\n",GetLastError());
    return;
}
char szBuf[100] = {0};
DWORD dwRead;
if(!ReadFile(hSlot,szBuf,100,&dwRead,NULL))
{
    MessageBox("Read Failed!!!");
    return;
}
MessageBox(szBuf);
CloseHandle(hSlot);

发送端

LPCTSTR szSlotName = TEXT("\\\\.\\mailslot\\Mymailslot");
HANDLE hMailSlot = CreateFile(szSlotName, FILE_GENERIC_WRITE, FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (hMailSlot == INVALID_HANDLE_VALUE)
{
	TRACE("CreateFile failed with %d\n",GetLastError());
	return;
}
char szBuf[100] = "MailSlot Comming";
DWORD dwWrite;
if (!WriteFile(hMailSlot,szBuf,strlen(szBuf)+1,&dwWrite,NULL))
{
	MessageBox("WriteFile Failed!!");
	return;
}
CloseHandle(hMailSlot);

无名管道通信

主进程创建管道

SECURITY_ATTRIBUTES sa;
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
if (!CreatePipe(&hReadPipe,&hWritePipe,&sa,0))
{
	MessageBox("匿名管道创建失败");
	return;
}
STARTUPINFO strStartupInfo;
memset(&strStartupInfo, 0, sizeof(strStartupInfo));
strStartupInfo.cb = sizeof(strStartupInfo);
strStartupInfo.dwFlags = STARTF_USESTDHANDLES;
strStartupInfo.hStdInput = hReadPipe;
strStartupInfo.hStdOutput = hWritePipe;
strStartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);

PROCESS_INFORMATION szProcessInformartion;
memset(&szProcessInformartion, 0, sizeof(szProcessInformartion));

int iRet = CreateProcess(_T("ProcessClient.exe"),
	NULL,
	NULL,
	NULL,
	TRUE,
	0,
	NULL,
	NULL,
	&strStartupInfo,
	&szProcessInformartion
);
if (iRet)
{
	CloseHandle(szProcessInformartion.hProcess);
	CloseHandle(szProcessInformartion.hThread);
	szProcessInformartion.dwProcessId = 0;
	szProcessInformartion.dwThreadId = 0;
	szProcessInformartion.hThread = 0;
	szProcessInformartion.hProcess = 0;
}
else
{
	CloseHandle(hReadPipe);
	CloseHandle(hWritePipe);
	hReadPipe = NULL;
	hWritePipe = NULL;
	MessageBox("创建子进程失败");
	return;
}

主进程发送

char szBuf[] = "This ";
DWORD dwWrite;
if (!WriteFile(hWritePipe,szBuf,strlen(szBuf)+1,&dwWrite,NULL))
{
	MessageBox("写入数据失败");
	return;
}

子进程接收

hReadPipe = GetStdHandle(STD_INPUT_HANDLE);
char szBuf[100] = { 0 };
DWORD dwRead;
if (!ReadFile(hReadPipe,szBuf,100,&dwRead,NULL))
{
	MessageBox("ReadFile Failed!!!");
	CloseHandle(hReadPipe);
	return;
}
MessageBox(szBuf);

子进程发送,主进程接收同理。

一个是GetStdHandle(STD_OUTPUT_HANDLE);

一个是GetStdHandle(STD_INPUT_HANDLE);

命名管道通信

服务端创建命名管道

LPCTSTR szPipeName = TEXT("\\\\.\\pipe\\mypipe");
hNamedPipe = CreateNamedPipe(szPipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE, 1, 1024, 1024, 0, NULL);
if (hNamedPipe == INVALID_HANDLE_VALUE)
{
	TRACE("CreateNamedhPipe failed with %d\n", GetLastError());
	MessageBox("创建命名管道失败");
	return;
}
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (NULL == hEvent)
{
	MessageBox("创建事件失败");
	CloseHandle(hNamedPipe);
	hNamedPipe = NULL;
	return;
}
OVERLAPPED ovlap;
ZeroMemory(&ovlap, sizeof(OVERLAPPED));
ovlap.hEvent = hEvent;
//等待连接
if (!ConnectNamedPipe(hNamedPipe,&ovlap))
{
	if (ERROR_IO_PENDING!=GetLastError())
	{
		MessageBox("等待客户端连接失败");
		CloseHandle(hNamedPipe);
		CloseHandle(hEvent);
		hNamedPipe = NULL;
		hEvent = NULL;
		return;
	}
}
if (WaitForSingleObject(hEvent,INFINITE)==WAIT_FAILED)
{
	MessageBox("等待对象失败");
	CloseHandle(hNamedPipe);
	CloseHandle(hEvent);
	hNamedPipe = NULL;
	hEvent = NULL;
	return;
}

客户端连接命名管道

LPCTSTR szNamedPipeName = TEXT("\\\\.\\pipe\\mypipe");
if (0 == WaitNamedPipe(szNamedPipeName,NMPWAIT_WAIT_FOREVER))
{
	MessageBox("当前没有可以利用的管道");
	return;
}
hNamedPipe = CreateFile(szNamedPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hNamedPipe == INVALID_HANDLE_VALUE)
{
	TRACE("CreateFile failed with %d\n", GetLastError());
	MessageBox("打开命名管道失败!");
	hNamedPipe = NULL;
	return;
}

接收端

char szBuf[100] = "Named Pipe Going Client";
DWORD dwRead;
BOOL bRet = ReadFile(hNamedPipe, szBuf, 100, &dwRead, NULL);
if (!bRet)
{
	MessageBox("ReadFile Failed!!!");
	CloseHandle(hNamedPipe);
	return;
}
MessageBox(szBuf);

发送端

char szBuf[100] = "Named Pipe Going Client";
DWORD dwWrite;
if (!WriteFile(hNamedPipe,szBuf,strlen(szBuf)+1,&dwWrite,NULL))
{
	MessageBox("WriteFile Failed!!!");
	CloseHandle(hNamedPipe);
	return;
}

WM_COPYDATA通信

最常用的通信方式

接收端

资源视图 --> 类向导 --> 消息 --> WM_COPYDATA

BOOL CServerReviewDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
	LPCTSTR szText = (LPCTSTR)(pCopyDataStruct->lpData);
	DWORD dwLength = (DWORD)pCopyDataStruct->cbData;
	TCHAR szRecvText[1024] = { 0 };
	memcpy(szRecvText, szText, dwLength);
	MessageBox(szRecvText, "Y", MB_OK);
	return CDialogEx::OnCopyData(pWnd, pCopyDataStruct);
}

发送端

CString strWindowsTitle = "服务端";
CString strMsg = "COPYDATA";
HWND hWnd = ::FindWindow(NULL, strWindowsTitle);
if (hWnd != NULL && IsWindow(hWnd))
{
	COPYDATASTRUCT cpd;
	cpd.dwData = 0;
	cpd.cbData = strMsg.GetLength() * sizeof(TCHAR);
	cpd.lpData = (PVOID)strMsg.GetBuffer(0);
	::SendMessage(hWnd, WM_COPYDATA, (WPARAM)(AfxGetApp()->m_pMainWnd), (LPARAM)&cpd);
}
strWindowsTitle.ReleaseBuffer();
strMsg.ReleaseBuffer();

Windows编程-文件&注册表

创建文件和读取文件的4种方法

c语言

FILE* pFile = fopen("1.txt", "w");  //w重写文件 a追加内容
if (pFile == NULL)
{
	MessageBox("打开失败");
	return;
}
char szBuf[1024] = "C语言文件操作";
int iLen = strlen(szBuf) + 1;
if (fwrite(szBuf, 1, strlen(szBuf) + 1, pFile) <= 0)
{
	MessageBox("写入失败");
	return;
}
fclose(pFile);
FILE* pFile = fopen("1.txt", "r");
if (pFile == NULL)
{
	MessageBox("打开失败");
	return;
}
char szBuf[1024] = { 0 };
//fseek函数 求文件的偏移量
fseek(pFile,0,SEEK_END);
int iFileLen = ftell(pFile); //得到文件指针的当前位置
fseek(pFile, 0, SEEK_SET);
int iLen = fread(szBuf,sizeof(char), iFileLen, pFile);
fclose(pFile);
MessageBox(szBuf);

c++

#include<io.h>
#include<fstream>
std::ofstream ofs("2.txt"); //表示文件名
char szBuf[1024] = "C++操作文件";
ofs.write(szBuf,strlen(szBuf));
ofs.close();
std::ifstream ifs("2.txt"); //表示文件名
char szBuf[1024] = { 0 };
ifs.read(szBuf, strlen(szBuf));
ifs.close();
MessageBox(szBuf);

windowsApi

HANDLE hFile;
hFile = CreateFile("3.txt",GENERIC_WRITE,NULL,NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
	MessageBox("创建文件对象失败");
	return;
}
//写文件
DWORD dwWrites;
char szBuf[1024] = "WIN API 操作文件";
WriteFile(hFile,szBuf,strlen(szBuf),&dwWrites,NULL);
TRACE("######dwWrites = %d\n", dwWrites);
CloseHandle(hFile);
HANDLE hFile;
hFile = CreateFile("3.txt", GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
	MessageBox("创建文件对象失败");
	return;
}
//读文件
DWORD dwWrites;
char szBuf[1024] = { 0 };
ReadFile(hFile, szBuf, 1024, &dwWrites, NULL);
TRACE("######dwWrites = %d\n", dwWrites);
CloseHandle(hFile);
MessageBox(szBuf);

mfc函数

CFile file("4.txt", CFile::modeCreate | CFile::modeWrite);
char szBuf[1024] = "MFC操作文件";
file.Write(szBuf, strlen(szBuf));
file.Close();
CFile file("4.txt", CFile::modeRead);
char szBuf[1024] = {0};
DWORD dwFilelen;
dwFilelen = file.GetLength();
file.Read(szBuf, dwFilelen);
file.Close();
MessageBox(szBuf);

对话框读取文件

CFileDialog fileDlg(TRUE);
fileDlg.m_ofn.lpstrTitle = "Test";
fileDlg.m_ofn.lpstrFilter = "Text Files(*.txt)\0*.txt\0All File(*.*)\0*.*\0\0";
if (IDOK == fileDlg.DoModal())
{
	CFile file(fileDlg.GetFileName(), CFile::modeRead);
	char szBuf[1024] = { 0 };
	DWORD dwFilelen;
	dwFilelen = file.GetLength();
	file.Read(szBuf, dwFilelen);
	file.Close();
	MessageBox(szBuf);
}

配置项的读写方法

//写配置项
char szPath[MAX_PATH] = { 0 };
GetCurrentDirectory(MAX_PATH, szPath);
char szMyPath[MAX_PATH] = { 0 };
sprintf(szMyPath, "%s\\Test.ini", szPath);

WritePrivateProfileString("Param1", "QueryInterval", "3600", szMyPath);
WritePrivateProfileString("Param1", "CheckInterval", "4000", szMyPath);
WritePrivateProfileString("Param2", "PopupInterval", "3000", szMyPath);
char szPath[MAX_PATH] = { 0 };
GetCurrentDirectory(MAX_PATH, szPath);
char szMyPath[MAX_PATH] = { 0 };
sprintf(szMyPath, "%s\\Test.ini", szPath);

char str1[MAX_PATH] = { 0 };
char str2[MAX_PATH] = { 0 };
char str3[MAX_PATH] = { 0 };
	
GetPrivateProfileString("Param1","QueryInterval",NULL, str1,1024,szMyPath);
GetPrivateProfileString("Param1","CheckInterval",NULL, str2,1024,szMyPath);
GetPrivateProfileString("Param2","PopupInterval",NULL, str3,1024,szMyPath);

CString strShow;
strShow.Format("QueryInterval = %s CheckInterval = %s PopupInterval = %s",str1,str2,str3);
MessageBox(strShow);

注册表的读写方法

HKEY hKey;
DWORD dwWeight = 80;
DWORD dwRet = ::RegCreateKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\MYWEIGHT\\admin", &hKey);
if (dwRet != ERROR_SUCCESS)
{
	MessageBox("创建注册表失败");
	return;
}
//写注册表
dwRet = ::RegSetValueEx(hKey, "weight", NULL, REG_DWORD, (CONST BYTE*) & dwWeight, 4);
if (dwRet != ERROR_SUCCESS)
{
	MessageBox("设置注册表失败");
}
::RegCloseKey(hKey);
HKEY hKey;
DWORD dwRet = ::RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\MYWEIGHT\\admin", &hKey);
if (dwRet != ERROR_SUCCESS)
{
	MessageBox("打开注册表失败");
}
DWORD dwWeight;
DWORD dwType;
DWORD dwSize;

dwRet = ::RegQueryValueEx(hKey,"weight",NULL,&dwType , (LPBYTE) & dwWeight, &dwSize);
CString strShow;
strShow.Format("Weight = %d", dwWeight);
if (dwRet != ERROR_SUCCESS)
{
	MessageBox("读注册表失败");
	return;
}
::RegCloseKey(hKey);
MessageBox(strShow);

Windows编程-动态链接库

创建动态链接库

暴露dll函数的两个方法

方法一:使用_declspec(dllexport)声明,extern “C” 是为了保证暴露的函数名为ave而非乱码

extern "C" _declspec(dllexport) int ave(int a, int b)
{
    return (a + b) / 2;
}

方法二:创建模块定义文件.def,并且声明函数名,不可以有重载 链接器->输入->模块定义文件

LIBRARY

EXPORTS
	ave

WinAPI往往会加上 __stdcall 函数名会变成 _ave@8

告诉编译器

#pragma comment (linker,"/export:ave=?_ave@8")

使用动态链接库

typedef int (*ave1)(int a, int b);
typedef int (WINAPI* ave)(int a, int b);
HMODULE hMod = LoadLibraryA("2.dll");
ave func = (ave1)GetProcAddress(hMod,"ave");
ave1 func1 = (ave1)GetProcAddress(hMod,"ave1");   
std::cout << func(200,300) << std::endl;
std::cout << func1(200,300) << std::endl;

Windows编程-MFC

画布

CDC* pDC = GetDC();

pDC->MoveTo(m_start);

pDC->LineTo(m_stop);

ReleaseDC(pDC);

InvalidateRect(NULL); //触发OnDraw

画笔

CPen pen(PS_DOT, 1, RGB(255, 0, 0));

CPen* pOldPen = pDC->SelectObject(&pen);

画刷

CBrush brush(RGB(255, 0, 0));

CBrush* pOldBrush = pDC->SelectObject(&brush);

pDC->FillRect(CRect(m_start, m_cur), &brush);

光标和文本

CSize sz = pDC->GetTextExtent();

CPoint pt;

SetCaretPos(pt);

pDC->TextOut();

Linux系统编程

字符串函数

头文件<ctype.h>

测试字符是否为英文字母或数字:

int isalnum(int c)

测试字符是否为英文字母:

int isalpha(int c)

测试字符是否为 ASCII 码字符:

int isascii(int c)

测试字符是否为空格字符:

int isblank(int c)

测试字符是否为 ASCII 码的控制字符:

int iscntrl(int c)

测试字符是否为阿拉伯数字:

int isdigit(int c)(int c)

测试字符是否为可打印字符:

int isgraph(int c)

测试字符是否为小写英文字母:

int islower(int c)

测试字符是否为可打印字符:

int isprint(int c)

测试字符是否为空格字符:

int isspace(int c)

测试字符是否为标点符号或特殊符号:

int ispunct(int c)

测试字符是否为大写英文字母:

int isupper(int c)

测试字符是否为 16 进制数字:

int isxdigit(int c)

数据转换函数

头文件<stdlib.h>

将字符串转换成浮点型数:

double atof(const char*)

将字符串转换成整型数:

int atoi(const char*)

将字符串转换成长整型数:

long int atol(const char*)

将字符串转换成 64 位整数(C++11)

long long int atoll ( const char * str )

将字符串转换成整数

long int strtol (const char* str, char** endptr, int base)

将字符串转换成无符号整数

unsigned long int strtoul (const char* str, char** endptr, int base)

将字符串转换成长整数(C++11)

long long int strtoll (const char* str, char** endptr, int base)

将字符串转换成无符号长整数(C++11)

unsigned long long int strtoull (const char* str, char** endptr, int base)

将字符串转换成浮点数(C++11)

float strtof (const char* str, char** endptr)

将字符串转换成双精度数

double strtod (const char* str, char** endptr)

将字符串转换成长双精度数(C++11)

long double strtold (const char* str, char** endptr)

将浮点型数转换成字符串:

char* ecvt(double value, int ndigit, int *decpt, int *sign)

ndigit 指的是全部的有效位数

将浮点型数转换为字符串:

char *fcvt(double value, int ndigit, int *decpt, int *sign)

ndigit 指的是小数点之后的有效位数

将浮点型数转换为字符串:

char *gcvt(double value, int ndigit, char *buf)

ndigit 指的是最大有效位数

Last Updated:
Contributors: wqby