C++实现简单的String类
最后更新于:2022-04-01 20:13:52
~~~
#include "stdafx.h"
#include
#include
// delete之后将指针置为NULL防止空指针
#define SAFE_DELETE(p) do{ if(p){delete(p); (p)=NULL;} }while(false);
class String
{
public:
// 普通构造函数
String(const char *str = NULL);
// 拷贝构造函数
String(const String &other);
// 赋值函数(重载操作符=)
String & operator = (const String &other);
// 析构函数
~String();
void printValue(){ printf("%s", m_pcData); };
private:
// 保存字符串的指针
char* m_pcData;
};
String::String(const char *str)
{
if (str == NULL)
{
m_pcData = new char[1];
m_pcData = "\0";
}
else
{
// strlen(str)是计算字符串的长度,不包括字符串末尾的“\0”
size_t len = strlen(str) + 1;
m_pcData = new char[len];
strcpy_s(m_pcData, len, str);
}
}
String::String(const String& other)
{
if (other.m_pcData == NULL)
{
m_pcData = "\0";
}
else
{
int len = strlen(other.m_pcData) + 1;
m_pcData = new char[len];
strcpy_s(m_pcData, len, other.m_pcData);
}
}
String& String::operator = (const String &other)
{
// 如果是自身就直接返回了
if (this == &other)
return *this;
// 判断对象里面的值是否为空
if (other.m_pcData == NULL)
{
delete m_pcData;
m_pcData = "\0";
return *this;
}
else
{
// 删除原有数据
delete m_pcData;
int len = strlen(other.m_pcData) + 1;
m_pcData = new char[len];
strcpy_s(m_pcData, len, other.m_pcData);
return *this;
}
}
String::~String()
{
SAFE_DELETE(m_pcData);
printf("\nDestory``````");
}
int main()
{
// 调用普通构造函数
String* str = new String("PWRD");
str->printValue();
delete str;
// 调用赋值函数
String newStr = "CDPWRD";
printf("\n%s\n", newStr);
String copyStr = String(newStr);
copyStr.printValue();
system("pause");
return 0;
}
~~~
调试结果:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7f0ab94.jpg)
';
fatal error C1083: 无法打开包括文件: “SDKDDKVer.h”: No such file or directory
最后更新于:2022-04-01 20:13:50
妈的,搞了毛大一歇!!
解决办法:
项目--右键--属性--配置属性--常规--平台工具集--选择Visual Studio 2013 -WindowsXP(v120_xp)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7edcf70.jpg)
';
C++的构造函数初始化列表
最后更新于:2022-04-01 20:13:47
在看人家源码的时候卡住了,看见初始化构造函数的时候看不懂了,构造函数的:,,,这些都是些啥。
纠结了半天,然后各种问各种搜资料,对这个东西理解了一部分了。
C++类中成员变量的初始化有两种方式:
构造函数初始化列表和构造函数体内赋值。下面看看两种方式有何不同。
~~~
#include "stdafx.h"
#include
using namespace std;
class Company
{
public:
// 构造函数初始化列表赋值
Company(string name,string addr,string boss,int staffCount)
:m_sName(name)
,m_sAddr(addr)
,m_sBoss(boss)
,m_nStaff(staffCount)
{
}
/*// 一般赋值的方法
Company(string name,string addr,string boss,int staffCount)
{
m_sName = name;
m_sAddr = addr;
m_sBoss = boss;
m_nStaff = staffCount;
}*/
private:
string m_sName;
string m_sAddr;
string m_sBoss;
int m_nStaff;
public:
void printInfo()
{
printf("The m_sName is = %s\n",m_sName.c_str());
printf("The m_sAddr is = %s\n",m_sAddr.c_str());
printf("The m_sBoss is = %s\n",m_sBoss.c_str());
printf("The m_nStaff is = %d\n",m_nStaff);
}
};
int main(int argc, _TCHAR* argv[])
{
Company* pComp = new Company("Ghgame","Tianfusoftwarepark","dinghq.alex",80);
pComp->printInfo();
delete pComp;
system("pause");
return 0;
}
~~~
还是看看打印:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7ec38c2.jpg)
通过打印来看初始化还是起到作用的(学程序就是多写多调试多打印看看结果嘛)。
但是,如果要是有一个子类,继承了它,子类构造函数的初始化就有点问题了。
~~~
class Subsidiary : public Company
{
public:
/*// 如果这样写是不行的,父类Company没有相同的构造函数
Subsidiary(string name,string addr,string boss,int staffCount,int createDate){};*/
Subsidiary(string name,string addr,string boss,int staffCount,int createDate)
// 必须使用初始化列表增加对父类的初始化
:Company(name,addr,boss,staffCount)
,m_nCreateDate(createDate)
{};
~Subsidiary(){};
private:
// 当子类需要多加自己的参数的时候就要显式调用父类的构造函数初始化
int m_nCreateDate;
};
~~~
这样一个子类如果要给父类赋值同时加上自己的m_nCreateDate属性,构造函数就不能像注释中那样用一般的构造函数来赋值了,要用构造函数的初始化列表来赋值。
也只理解到这里。
';
快速排序
最后更新于:2022-04-01 20:13:45
实际中最常用的一种排序算法,速度快,效率高,就像它的名字一样。
快速排序的基本思想是:
1.先从数列中取出一个作为基准。
2.分区过程,将比这个基数大的数全部放在它的右边,小于或者等于它的数就全部放在它的左边。
3.然后再对左右区间重复第二步,直到各区都只有一个数就排序完成了。
实现的代码:
~~~
#include "stdafx.h"
#include
void quickSort(int a[], int start, int length)
{
if (start < length)
{
int i, j, key;
i = start;
j = length;
// 基准数
key = a[start];
while (i < j)
{
// 从右向左找第一个小于基数(key)的那个数
while(i < j && a[j] >= key)
j--;
if(i < j)
a[i++] = a[j];
// 从左向右找第一个大于等于基数(key)的那个数
while(i < j && a[i] < key)
i++;
if(i < j)
a[j--] = a[i];
}
// 当i=j就把找到的基数放好位置了
a[i] = key;
// 递归下一个直到i=j
quickSort(a, start, i - 1);
quickSort(a, i + 1, length);
}
}
int main(int argc, _TCHAR* argv[])
{
int a[] = {5,6,2,3,1,8,9,0,1};
int len = sizeof(a)/sizeof(*a);
quickSort(a,0,len-1);
for (int i=0;i
';
VS看反汇编、寄存器、内存、堆栈调用来学习程序设计
最后更新于:2022-04-01 20:13:43
其实计算机这套东西是真的太大了,太多的东西要学要理解更要掌握。学习的时候要不断拆分理解才能掌握得更牢固,学程序的时候可以通过查看程序的运行过程进而加深自己对程序设计的理解,调试出最优化的代码。
VisualStudio,微软的高端IDE,可以写C/C++可以搞C#,写网站等等,牛逼得爆。VS调试方便功能强大,一般公司也用它来开发。我在学C++的时候也是用的VS,在学习的时候感觉用到的东西越来越多,还没理解得东西也很多,比如说堆栈调用,寄存器等等,然后就了解到VS的反汇编、寄存器、内存、堆栈调用等等功能窗口。
好了 还是来扯怎么看这些东西,怎么玩
### 1. 首先还是新建一个C/C++项目,然后找个地方打上断点
我项目代码是这样的,我在第5行的时候加了个断点,然后让程序停下来了。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7de74f2.jpg)
### 2. 用快捷键看反汇编、寄存器、内存、堆栈调用
下面是快捷键
ALT+5是寄存器窗口:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7e0fb1b.jpg)
ALT+6是内存地址窗口:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7e24ad0.jpg)
ALT+7是调用堆栈的窗口(在程序很大的时候通过堆栈调用窗口来看程序在哪个函数停止的,里面变量哪里是空指针等,是很有用的):
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7e3f606.jpg)
ALT+8是反汇编窗口:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7e68dcf.jpg)
都说项目是调试出来的,是真的,写代码写得开心,可能在加班玩命堆代码的时候功能倒是做完了,但是有些地方有很多隐形的Bug,或许是同时忘了判断空指针等等小问题,程序老是崩溃,调试代码的技术还是很有必要的。
用这几个窗口可以快速定位问题和学习C++和计算机系统。
';
C语言冒泡排序
最后更新于:2022-04-01 20:13:41
冒泡是一个程序员接触最早的一个排序算法,简单粗暴。
冒泡排序的核心思想就是:依次比较相邻的两个数,如果第一个数比第二个大就交换。
程序还是要自己动手写,这样理解得才快。
~~~
#include "stdafx.h"
#include
#include
#define SIZE(A) sizeof(A)/sizeof(*A)
using namespace std;
void bubbleSort(int a[],int size)
{
// 两个循环来比较相邻的两个数,如果前一个比后面一个大就交换
for (int i=0;ia[j])
{
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
}
}
int main() {
int a[] = {2,6,8,1,0,3,4};
int size = SIZE(a);
for (int i=0;i
';
顺序存储结构和链式存储结构的选择
最后更新于:2022-04-01 20:13:38
容器的存储分为顺序存储和链式存储
## 一、顺序存储结构
从数据结构的角度来说顺序储存结构的存储空间固定,可扩展性差,但是如果数据元素个数已知,较链式存储节省空间。
它的优点是随机读取任意一个元素(因为元素时顺序存储的,所以元素的存储位置之间有一定的关系)但是顺序存储的缺点是删除、插入操作需要花费很多时间在移动元素上。
STL中的vector就是典型的顺序存储结构。
## 二、链式存储结构
对于链式存储而言,插入和删除元素开销小,操作简便,最大特点就是插入、删除运算方便,可扩展性强。STL中list、map就是典型的链式存储结构。
下面是它的特点:
1.比顺序存储结构的存储密度小 (每个节点都由数据域和指针域组成,所以相同空间内假设全存满的话顺序存储比链式存储能存得更多)。
2.逻辑上相邻的节点物理上不必相邻。
3.插入、删除灵活 (不必移动节点,只要改变节点中的指针)。
4.查找结点时链式存储要比顺序存储慢。
5.每个结点是由数据域和指针域组成。
## 三、结论
综上所述,如果元素个数已知,且插入删除较少的可以使用顺序结构,而对于频繁有插入删除操作,元素个数未知的,最好使用链式结构,编程时可结合要处理的数据的特点选择或设计数据结构的。
';
pthread的pthread_join()函数理解实验
最后更新于:2022-04-01 20:13:36
### 一、使用方式
> pthread_t tid;
> pthread_create(&tid, NULL, thread_run,NULL);
> pthread_join(tid,NULL);
创建线程之后直接调用pthread_join方法就行了。
### 二、为什么要使用pthread_join()
在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到pthread_join()方法了。
即pthread_join()的作用可以这样理解:主线程等待子线程的终止。也就是在子线程调用了pthread_join()方法后面的代码,只有等到子线程结束了才能执行。
### 三、代码实验
可以通过代码来看看执行的效果,就知道了:
~~~
#include "stdafx.h"
#include
#include
#include
#pragma comment(lib, "pthreadVC2.lib")
static int count = 0;
void* thread_run(void* parm)
{
for (int i=0;i<5;i++)
{
count++;
printf("The thread_run method count is = %d\n",count);
Sleep(1000);
}
return NULL;
}
int main()
{
pthread_t tid;
pthread_create(&tid, NULL, thread_run,NULL);
// 加入pthread_join后,主线程"main"会一直等待直到tid这个线程执行完毕自己才结束
// 一般项目中需要子线程计算后的值就需要加join方法
pthread_join(tid,NULL);
// 如果没有join方法可以看看打印的顺序
printf("The count is = %d\n",count);
getchar();
return 0;
}
~~~
加了pthread_join()方法的打印:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7d90e3c.jpg)
如果把里面的pthread_join()方法注释掉的打印:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7daf658.jpg)
可以看得出来,如果没有加pthread_join()方法,main线程里面直接就执行起走了,加了之后是等待线程执行了之后才执行的后面的代码。
';
Windows上VS使用pthread重温经典多线程卖票(pthreads-w32-2-8-0-release.exe)(windows上使用pthread.h)
最后更新于:2022-04-01 20:13:34
### 一、安装pthreads-w32-2-8-0-release.exe
> 至于怎么下载这个exe还是自己去百度吧,CSDN上反正可以下载,而且有免费版本的,资源里面搜下就行了。
然后安装这个就行了(按照下面的123来就行了)。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7d36a61.jpg)
然后Pre-built.2这个文件夹有个include和lib,把它们分别拷贝到C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC这个目录下面的include和lib下面。
### 二、 卖票的例子程序
~~~
#include "stdafx.h"
#include
#include
#include
#pragma comment(lib, "pthreadVC2.lib")
// ticket count
int m_nTicket = 100;
// thread lock
pthread_mutex_t m_tMutex = PTHREAD_MUTEX_INITIALIZER;
void* thread_run(void* parm)
{
while(true)
{
//pthread_mutex_lock(&m_tMutex);
if (m_nTicket>0)
{
Sleep(1000);
printf("第%d号窗口卖出了第",pthread_self());
printf("%d张票。\n",m_nTicket);
m_nTicket--;
}else{
break;
}
//pthread_mutex_unlock(&m_tMutex);
}
return NULL;
}
int main()
{
pthread_t tid1;
pthread_t tid2;
// create two threads
pthread_create(&tid1, NULL, thread_run,NULL);
pthread_create(&tid2, NULL, thread_run,NULL);
/*// 加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行
// 调用pthread_self()函数可以获得自身的线程号
pthread_join(pthread_self(),NULL);*/
getchar();
return 0;
}
~~~
嘿,两个线程卖票的例子。
这里面涉及到线程的互斥,还需要自己去琢磨。
互斥就是两个线程使用同一个变量的时候数据可能会发生异常,这时候就要加锁了,那个数据只能让一个线程使用。
可以自己试验一下。
下面是调试的结果:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7d59d4a.jpg)
对了,如果编译的时候发生这个错误:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7d769ad.jpg)
在解压出来的Pre-built.2\lib文件夹里面把这个pthreadVC2.dll复制到C:\Windows\SysWOW64这里面去,32位的就对应的复制到32位那个文件夹。
';
strcat函数实现
最后更新于:2022-04-01 20:13:32
其实也是在面试的时候遇到的这个问题
回来了郁闷到了查了下这函数
哎 以前学Java的
没见到过这些世面
终于还是看到这些基础的函数了
试着写了下这个strcat函数,挺简单的
~~~
char* _strcat_d(char* dest, char* src)
{
char* newStr = dest;
if (dest == NULL || src == NULL)
return NULL; // 如果有空的就扔个空指针出去
while (*dest != '\0')
dest++; // 找到'\0'结束的位置,把指针指向最后的那个字符
while (*dest++ = *src++);
return newStr;
}
~~~
在main函数试试
~~~
int main()
{
char k[256] = "ChengDu";
char p[256] = "Neusoft";
// 把p链接在k的后面
_strcat_d(k,p);
cout << k <
';
strupr与strlwr函数的实现
最后更新于:2022-04-01 20:13:29
strupr函数用来将指向的字符串全部转换为大写的形式
strlwr函数则用来将指向的字符串全部转换为小写的形式
实现这样两个函数也比较简单
还是先贴代码出来
首先是strupr函数:
~~~
// 字符全部转换为大写
char* _strupr_d(char* src)
{
while (*src != '\0')
{
if (*src >= 'a' && *src <= 'z')
//在ASCII表里大写字符的值比对应小写字符的值小32.
//*p -= 0x20; // 0x20的十进制就是32
*src -= 32;
src++;
}
return src;
}
~~~
这里面注释已经写得很清楚了,相应的,如果要转换成小写的,+=32就行了,下面是strlwr函数的实现:
~~~
// 字符全部转换为小写
char* _strlwr_d(char* src)
{
while (*src != '\0')
{
if (*src > 'A' && *src <= 'Z'){
//*src += 0x20;
*src += 32;
}
src++;
}
return src;
}
~~~
然后通过函数调用可以试试效果:
~~~
int main()
{
char *p = "Neusoft";
char k[256];
// 字符串全部转成大写
_strupr_d(k);
// 字符串转换成小写
//_strlwr_d(k);
cout << k <
';
Java使用JNI调用C++的完整流程
最后更新于:2022-04-01 20:13:27
JNI其实是Java NativeInterface的简称,也就是java本地接口。它提供了若干的API实现了和Java和其他语言的通信(主要是C/C++)。
Java以其跨平台的特性深受人们喜爱,而又正由于它的跨平台的目的,使得它和本地机器的各种内部联系变得很少,约束了它的功能。解决Java对本地操作的一种方法就是JNI。
Java通过JNI调用本地方法,而本地方法是以库文件的形式存放的(在WINDOWS平台上是DLL文件形式,在UNIX机器上是SO文件形式)。通过调用本地的库文件的内部方法,使JAVA可以实现和本地机器的紧密联系,调用系统级的各接口方法。
在项目中管理很好一般可以不使用JNI,但是有时候必须用到的时候就要掌握一下这个东西了。其实Java很多的源码里面的一些算法都是Native声明的,也就是这些方法很可能是在C/C++中实现了的。
我没事做的时候自己单独重新做了一遍JNI使用过程,分享一下流程。
1. 首先在Eclipse建立一个Java类,在这个类中用native关键字声明需要用到的函数
下面是我自己写的测试代码
~~~
package com.ghgame.javausecpp;
public class JavaUseCpp {
public JavaUseCpp() {
System.out.println(">>>>>JNI Test Start<<<<<");
}
static {
}
public native void printStr(String str);
}
~~~
2. 在DOS窗口下生产C++的h文件
这里使用命令来完成
去找到Eclipse新建的工程目录下的bin文件夹,bin存放编译好的class文件;
然后cd 到这个E:\WorkAndroid\JNITest\bin目录
在dos下输入命令
E:\WorkAndroid\JNITest\bin>javah-classpath . -jni com.ghgame.javausecpp. JavaUseCpp
然后在E:\WorkAndroid\JNITest\bin即可找到一个com_ghgame_javausecpp_JavaUseCpp.h头文件,生成成功!
注意:com.ghgame.javausecpp是包名,JavaUseCpp是类名
下面是生成的h文件截图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7bb80e0.jpg)
这里javah的用法:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7bcfd2e.jpg)
到这里Java部分就算是差不多了。
可以打开看看刚刚生成的这个**com_ghgame_javausecpp_JavaUseCpp.h**头文件
~~~
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class com_ghgame_javausecpp_JavaUseCpp */
#ifndef _Included_com_ghgame_javausecpp_JavaUseCpp
#define _Included_com_ghgame_javausecpp_JavaUseCpp
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_ghgame_javausecpp_JavaUseCpp
* Method: printStr
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_ghgame_javausecpp_JavaUseCpp_printStr
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
~~~
这里面注释上标注类名、方法名、签名(Signature),签名这个东西下面来说。在这里最重要的是Java_com_ghgame_javausecpp_JavaUseCpp_printStr这个方法签名。在Java端我们执行 printStr(String str)这个方法之后,JVM就会帮我们调用在DLL里的Java_com_ghgame_javausecpp_JavaUseCpp_printStr这个方法。因此我们新建一个C++source file来实现这个方法。
还是扯哈签名这个东西。
为什么会有方法签名这种东西呢?这是因为Java这边支持函数重载,即虽然参数不一样,但是方法名一样,那么在JNI层它们的方法名都会是一样的,那JNI也会犯迷糊了,得找哪个呢?
不过也正是因为其参数类型是不一样的,所以就出现了方法签名,利用方法签名和方法名来唯一确定一个JNI函数的调用。
既然方法签名是基于参数类型的不同而形成的,首先要知道Java各数据类型对应的签名是什么,也就是所谓的类型签名,
在jni.h文件中就已经定义了这样一套规则,如下:
~~~
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
~~~
具体不清楚的还是找度娘问其他先生。
3. 写CPP文件,编译出DLL
建项目具体步骤就不说了。
新建一个Win32Cpp就行了,里面选dll,然后把刚才编译出来的h文件复制到C++工程的根目录去
然后新建一个**com_ghgame_javausecpp_JavaUseCpp.cpp**Cpp文件
在这里面实现h文件中的方法,我这里就打印了一下Java传过来的值而已
~~~
#include "stdafx.h"
#include "com_ghgame_javausecpp_JavaUseCpp.h"
JNIEXPORT void JNICALL Java_com_ghgame_javausecpp_JavaUseCpp_printStr(JNIEnv* env, jobject obj, jstring str)
{
const char* pTempStr = env->GetStringUTFChars(str, NULL);
printf(">>>>>>>>>>>>>>>The input string is = [ %s ]<<<<<<<<<<<<<<",pTempStr);
}
~~~
这里编译的时候值得注意的几个问题:
a. 需要另外两个头文件
都在jdk的安装目录下,有个include文件夹,把include文件夹下面的jni.h复制到C++工程的根目录,然后把com_ghgame_javausecpp_JavaUseCpp.h文件的#include 改成#include "jni.h"就行了。
还有个在include里面有个win32文件夹,jni_md.h这个头文件也复制到C++工程的根目录。
b. 在编译项目时候要注意选择平台,如果你jdk,机子都是64位你就编译64位的dll,32位同理。否则就是这个错误:JNI Can't load IA 32-bit .dll on a AMD 64-bit platform
编译出来的内容就是这样:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7beca10.jpg)
4. 配置Dll
这里加载的方法有两个,
a. 把刚刚生成的Dll扔到C盘Sysytem32或者SysWOW64文件夹下
b. 把C++工程下生成dll的目录设置到环境变量path中去(我是64位的机子就把x64的配置到Path去了)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7c1a579.jpg)
这个设置看个人喜好了
5. Java加载Dll,完成
到最后一步了,在Java里面加载Dll库,调用c++实现的方法
~~~
package com.ghgame.javausecpp;
public class JavaUseCpp {
public JavaUseCpp() {
System.out.println(">>>>>JNI Test Start<<<<<");
}
// 加载生成的DLL库文件
static {
System.loadLibrary("LibJniTest");
}
public native void printStr(String str);
public static void main(String[] args) {
String cName = "Ghgame";
// 传个值试试效果
new JavaUseCpp().printStr(cName);
}
}
~~~
下面是我执行的结果:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7c4c911.jpg)
感觉还是挺66666的。
好了,GG了,收拾东西准备下班。
';
C++的error LNK2019: 无法解析的外部符号编译错误
最后更新于:2022-04-01 20:13:25
~~~
1>Proxy.obj : error LNK2019: 无法解析的外部符号 __imp__connect@12,该符号在函数 "public: enum ProxyStatus __thiscall CProxy::ConnectProxyServer(unsigned int)" (?ConnectProxyServer@CProxy@@QAE?AW4ProxyStatus@@I@Z) 中被引用
1>Proxy.obj : error LNK2019: 无法解析的外部符号 __imp__ioctlsocket@12,该符号在函数 "public: enum ProxyStatus __thiscall CProxy::ConnectProxyServer(unsigned int)" (?ConnectProxyServer@CProxy@@QAE?AW4ProxyStatus@@I@Z) 中被引用
1>Proxy.obj : error LNK2019: 无法解析的外部符号 __imp__htons@4,该符号在函数 "public: enum ProxyStatus __thiscall CProxy::ConnectProxyServer(unsigned int)" (?ConnectProxyServer@CProxy@@QAE?AW4ProxyStatus@@I@Z) 中被引用
1>Proxy.obj : error LNK2019: 无法解析的外部符号 __imp__inet_addr@4,该符号在函数 "private: enum ProxyStatus __thiscall CProxy::ConnectBySock4(unsigned int,class std::basic_string,class std::allocator >,unsigned short)" (?ConnectBySock4@CProxy@@AAE?AW4ProxyStatus@@IV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@G@Z) 中被引用
1>Proxy.obj : error LNK2019: 无法解析的外部符号 __imp__ntohs@4,该符号在函数 "private: enum ProxyStatus __thiscall CProxy::ConnectBySock4(unsigned int,class std::basic_string,class std::allocator >,unsigned short)" (?ConnectBySock4@CProxy@@AAE?AW4ProxyStatus@@IV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@G@Z) 中被引用
1>Proxy.obj : error LNK2019: 无法解析的外部符号 __imp__recv@16,该符号在函数 "private: int __thiscall CProxy::Receive(unsigned int,char *,int)" (?Receive@CProxy@@AAEHIPADH@Z) 中被引用
1>Proxy.obj : error LNK2019: 无法解析的外部符号 __imp__select@20,该符号在函数 "public: enum ProxyStatus __thiscall CProxy::ConnectProxyServer(unsigned int)" (?ConnectProxyServer@CProxy@@QAE?AW4ProxyStatus@@I@Z) 中被引用
1>Proxy.obj : error LNK2019: 无法解析的外部符号 __imp__send@16,该符号在函数 "private: bool __thiscall CProxy::Send(unsigned int,char const *,int)" (?Send@CProxy@@AAE_NIPBDH@Z) 中被引用
1>Proxy.obj : error LNK2019: 无法解析的外部符号 __imp__setsockopt@20,该符号在函数 "public: enum ProxyStatus __thiscall CProxy::ConnectServer(unsigned int,class std::basic_string,class std::allocator >,unsigned short)" (?ConnectServer@CProxy@@QAE?AW4ProxyStatus@@IV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@G@Z) 中被引用
1>E:\WorkVS2012\SocketProxy\Debug\SocketProxy.exe : fatal error LNK1120: 9 个无法解析的外部命令
1>
1>生成失败。
~~~
error LNK2019: 无法解析的外部符号
出现这个错误一般都是函数只找到声明但没有实现,或者是少了什么链接库,可以试试把那两个.h和.c文件直接加入工程中再试试,或者是有些函数后面把{}加上。
比如一些函数声明了,像下面这段代码:
~~~
class A
{
public:
A();
virtual ~A();
char m_x;
};
~~~
这里面构造函数和析构函数都没有加上{}。加上即可。
~~~
class A
{
public:
A(){};
virtual ~A(){};
char m_x;
};
~~~
或者你另外实现这个函数。
还有种情况就是没有添加库
这种情况下加一句代码就行了
我遇到的是Socket相关的库没加上,我加上下面的部分就对了
~~~
#pragma comment(lib, "ws2_32.lib") //这是链接API相关连的Ws2_32.lib静态库
~~~
一般加在stdafx.h里面就好了
如果出现这种情况就检查下有没有这玩意了
LINK : fatal error LNK1104: cannot open file "ws32_2.dll"
下面是地址,看看有没有
C:\Program Files\Microsoft Platform SDK for Windows Server 2003 R2\Bin\Ws2_32.dll
C:\WINDOWS\system32\ws2_32.dll
C:\WINDOWS\system32\dllcache\ws2_32.dll
';
Cocos2dx实现翻牌效果(CCScaleTo与CCOrbitCamera两种方式)
最后更新于:2022-04-01 20:13:23
由于项目需要实现翻牌的效果,所以自己在完成的过程中将这篇文章写下来,想想还是觉得有点艰辛。
开始在网上找解决的办法找了很久,基本上就是一种解决方案,就是用CCOrbitCamera这个Action类来模拟实现翻牌的效果。
但是我在使用的效果中始终不如人意。
用CCOrbitCamera类实现倒是能实现,但是如果将牌移动到左上、左下或者其他不在屏幕中心的位置那这个效果就不行了,翻牌的位置就错误了,类似3D的了。
找了半天终于知道是什么原因了,Cocos2dX里面有这样的一句话,
CCDirector::sharedDirector()->setProjection(kCCDirectorProjection3D);
就是这个3D,Cocos2DX支持透视投影和正交投影两种模式,于是乎我把kCCDirectorProjection3D改成kCCDirectorProjection2D,对了,有感觉了。
这样一设置翻牌的动作就不是3D感了,就是正常的这种翻牌的感觉了。
但是项目中其他的图片出现了锯齿,这就不能忍了。
在用了很多办法都不能消除锯齿的时候我尝试到去读Cocos2dx关于设置setProjection函数的内容,看是否能够将锯齿避免,让翻转变得自然。
最终我失败了,确实还是太菜了不行。
我于是找替代方案来实现翻牌的动作,就找到了CCScaleTo来实现,这过程确实还是比较艰辛。
下面我把两种方案的关键代码都贴出来,方便大家看看吧。
1. 首先还是用CCOrbitCamera类实现翻牌的效果(我封装成了一个函数)。
~~~
void HelloWorld::showObtAniUseOrbitCamera( CCSize visibleSize )
{
if (m_pCardFront != NULL){
this->removeChild(m_pCardFront);
}
if (m_pCardBack != NULL){
this->removeChild(m_pCardBack);
}
// 加载牌的正反两面
m_pCardFront = CCSprite::create("CardFront.png");
m_pCardBack = CCSprite::create("CardBack.png");
/*m_pCardFront->setPosition(ccp(visibleSize.width/2-100,visibleSize.height/2+100));
m_pCardBack->setPosition(ccp(visibleSize.width/2-100,visibleSize.height/2+100));*/
m_pCardFront->setPosition(ccp(visibleSize.width/2,visibleSize.height/2));
m_pCardBack->setPosition(ccp(visibleSize.width/2,visibleSize.height/2));
this->addChild(m_pCardFront,5);
this->addChild(m_pCardBack,5);
// 动画序列(延时,显示,延时,隐藏)
CCSequence *pBackSeq = CCSequence::create(CCDelayTime::create(0.5f),CCShow::create(),CCDelayTime::create(0.5f),CCHide::create(),NULL);
//持续时间、半径初始值、半径增量、仰角初始值、仰角增量、离x轴的偏移角、离x轴的偏移角的增量
CCOrbitCamera *pBackCamera = CCOrbitCamera::create(1.0f, 1, 0, 0, -90, 0, 0);
CCSpawn* pSpawnBack = CCSpawn::create(pBackSeq,pBackCamera,NULL);
m_pCardBack->runAction(pSpawnBack);
// 动画序列(延时,隐藏,延时,显示)
CCSequence *pFrontSeq = CCSequence::create(CCDelayTime::create(0.5f),CCHide::create(),CCDelayTime::create(0.5f),CCShow::create(),NULL);
CCOrbitCamera *pLandCamera = CCOrbitCamera::create(1.2f, 1, 0, 0, -360, 0, 0);
CCSpawn* pSpawnFront = CCSpawn::create(pFrontSeq,pLandCamera,NULL);
m_pCardFront->runAction(pSpawnFront);
}
~~~
上面的注释写得比较详尽了,其实不难,就是背面显示的时候把正面隐藏,正面显示了把背面隐藏,就是这么个事。
这里面有个-90,-360是旋转的角度,可以自己尝试改了看会有什么效果。
下面是运行的效果图:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7af316a.jpg)
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-19_57b6ce7b2c464.jpg)
就是这样由背面翻转到正面,但注意,在“3D模式”下,position是设置在屏幕中间才会有这个效果,如果设置在其他位置就不是这个效果了,可以自己试试。
如果设置成2D模式就不会有这个情况了,但是不知道你自己的项目图片会不会出现锯齿,出现锯齿的情况我没有找到解决的办法。
2. 用CCScaleTo实现的翻转
就是因为有锯齿的情况出现我又没解决到,所以我在需找替代方案,于是乎找到了CCScaleTo来实现。
下面是关键的代码:
~~~
void HelloWorld::showObtAniUseScaleTo(CCSize visibleSize)
{
if (m_pCardFront != NULL){
this->removeChild(m_pCardFront);
}
if (m_pCardBack != NULL){
this->removeChild(m_pCardBack);
}
// 加载牌的正反两面
m_pCardFront = CCSprite::create("CardFront.png");
m_pCardBack = CCSprite::create("CardBack.png");
// 把牌反转了
m_pCardFront->setFlipX(true);
m_pCardFront->setPosition(ccp(visibleSize.width/2-100,visibleSize.height/2+100));
m_pCardBack->setPosition(ccp(visibleSize.width/2-100,visibleSize.height/2+100));
this->addChild(m_pCardFront,5);
this->addChild(m_pCardBack,5);
// 动画序列(延时,隐藏,延时,隐藏)
CCSequence *pBackSeq = CCSequence::create(CCDelayTime::create(0.5f),CCHide::create(),CCDelayTime::create(0.5f),CCHide::create(),NULL);
CCScaleTo* pScaleBack = CCScaleTo::create(1.2f,-1,1);
CCSpawn* pSpawnBack = CCSpawn::create(pBackSeq,pScaleBack,NULL);
m_pCardBack->runAction(pSpawnBack);
// 动画序列(延时,显示,延时,显示)
CCSequence *pFrontSeq = CCSequence::create(CCDelayTime::create(0.5f),CCShow::create(),CCDelayTime::create(0.5f),CCShow::create(),NULL);
CCScaleTo* pScaleFront = CCScaleTo::create(1.2f,-1,1);
CCSpawn* pSpawnFront = CCSpawn::create(pFrontSeq,pScaleFront,NULL);
m_pCardFront->runAction(pSpawnFront);
}
~~~
这里主要就是要先把牌的正面setFlipX(true)这个函数来翻转一下,然后再绕Y轴模拟个旋转出来,如果还是不怎么清楚还请看官自己写代码试试效果。
CCScaleTo参数的设置主要就是为了绕Y来旋转,具体的各种情况也可以自己试试效果,实践是检验真理的唯一标准嘛,可以设置成(1,1),(1,-1)等等试试效果。
好了,差不多就是这样一个情况。
可以自己试试写个HelloWord调效果,加深印象。
';
do{}while(0)的妙用
最后更新于:2022-04-01 20:13:20
其实关注do{}while(0)的用法还是一次偶然的机会
当时还在实习,连个工作都找不到,面试的时候有个面试题。
请问do{}while(0)这样写有什么作用?
我当时想这有什么作用,就是里面的代码块执行一遍就是了呗,费劲,胡乱答了几句在上面。
虽然那公司把我录了,但是真的还是很坑的一家公司,呆了两个月自己闪人了。
后面觉得这问题奇怪,就在网上搜了很多关于do{}while(0)用法的文章来看。
总体的作用就是一下几个方面:
1. 辅助定义复杂的宏,避免引用的时候出错
下面是Cocos2dx的一个宏定义:
~~~
#define CC_SAFE_DELETE(p) do { if(p) { delete (p); (p) = 0; } } while(0)
~~~
不得不说Cocos2dx的源码写得还是很严谨的
但是假设这里去掉do{}while(0)和里面的if(p)然后变成下面的这个样子:
~~~
#define CC_SAFE_DELETE(p) delete (p); (p) = 0;
~~~
如果用这个宏定义在代码中使用,像以下代码
~~~
if(NULL != p)
CC_SAFE_DELETE(p)
else
wordOff();
~~~
这段代码就有问题了:
a. 因为if分支后面有两条语句了,所以编译就会出问题。这里如果养成好习惯别装大神,加上一对{}这个问题也可以解决。
b. 还有个问题就是如果没有else分支里面的(p)=0会永远执行。因为if后面没括号只执行紧邻的一句代码。
我们在写代码的时候可以让代码严谨细致一点,加一点括号多两行,但是你以后修改Bug不会那么痛苦。
2. 跳出代码块,不执行后面的语句
其实这个功能比较实用一些
很多的条件判断可以用到do{}while(0)的写法
这个用法其实也很简单,精髓就在于用break或者continue来控制流程,跳出代码块。
就像下面的一串代码,看看就能明白了:
~~~
bool bFlag = false;
do {
if (!bFlag) {
print("The flag is FALSE");
break;// 用break(continue)跳出了之后就不用执行下面的语句了
//continue;
}
print("Work off.\n");
} while (0);
~~~
这里的break跳出循环就有点类似于goto语句了,不过goto太粗糙。
可以自己写个do{}while(0)的代码语句来调试调试,自然而然就领悟了。
还是邓大爷那句话啊,实践是检验真理的唯一标准。
下面是我的项目中通信块用到的do{}while(0)的示例,可以看看加深印象。
~~~
do
{
BYTE byProtocol = *(LPBYTE)pMsg;
if (byProtocol <= S2C_BEGIN || byProtocol >= S2C_END){
printf("Invalid Protocol %d\n", (INT)byProtocol);
assert(false);
continue;
}
INT nProSize = GetProtocolSize(byProtocol);
if (nProSize == -1){
nProSize = PROTOCOL_MSG_SIZE + (*(USHORT*) (((CHAR*)pMsg) + PROTOCOL_MSG_SIZE));
}
printf(">>>>ProtocolType %d, Size:%d != ProSize:%d\n", (INT)byProtocol, nSize, nProSize);
//协议出错跳出
if (nProSize != nSize){
printf("Invalid ProtocolType %d, Size:%d != ProSize:%d\n", (INT)byProtocol, nSize, nProSize);
assert(false);
continue;
}
//网络断开
if (!ProcessFunc[byProtocol]){
assert(false);
continue;
}
if(GHLandProtocol::m_pGameSence!=NULL){
(this->*ProcessFunc[byProtocol])(pMsg, nSize);
}
} while(FALSE);
~~~
好了,关于do{}while(0)我也就只学习到这里。
';
结构体内存对齐后所占内存空间大小的计算
最后更新于:2022-04-01 20:13:18
在项目开发中,很多时候其实会有用到sizeof一个结构体,具体什么时候用到呢。
比如说有这样一个结构体:
~~~
typedef struct tagOutCard
{
short UserId; // 用户ID
int byCardCount; // 出牌数目
int byCardData[20]; // 扑克列表
}ST_OUT_CARD;
~~~
这是一个棋牌游戏中玩家出牌的数据包,需要将它赋值之后发送到服务器。
那么就会用到sizeof(ST_OUT_CARD)。
那到底这个ST_OUT_CARD是多大呢?
下面来说说怎么计算这个东西。
因为计算机为了加快读写数据的速度,编译器就实现了数据对齐的做法来为每一个结构体分配空间。这里就涉及到一个内存对齐的计算了。
下面用一个直观一点的结构体来示例计算:
~~~
typedef struct Data
{
char a; // 1 // 会扩展到4
float b; // 4
double c; // 8
long long d; // 8
long double e; // 8
long f; // 4
short g; // 2 // 会扩展到4
int h; // 4
bool i; // 1
}ST_DATA; // 最后结构体会是8的倍数
~~~
有这样一个包含了基本类型的Data结构体。
要想sizeof(ST_DATA)求得大小就要看内存分配了,下面就是计算这个结构体大小的过程:
按照自上向下的为结构体分配空间,并在它们之间作调整。首先为char分配一个空间,接着编译器为了实现数据的对齐,在分配float的时候会对之前所分配的空间进行一些调整,调整方式是按照原先分配的空间的大小和当前要分配的大小来决定将要分配的空间,因为float为4字节(可以自己sizeof(float)来看),所以编译器要求在float之前所分配的空间大小应该是当期要分配的空间的倍数,在这里也就是说在float前面分配的应该是4的倍数空间才行,所以原来的char被迫扩展到4个字节,然后才为float分配4个字节,这样这两个变量就占了8字节。由于double类型的数据占8个字节,是4的倍数,所以就直接相加。
现在就是a(4)+b(4)+c(8)+d(8)+e(8) +f(4)= 36。在int这个类型的时候这里也会是将short类型的内存字节扩展到4。
所以到最后的时候就是36+g(4)+h(4)+i(1) = 45。
以为这里就是对齐好了吗?sizeof出来就是45了吗?
其实还有一个就是编译器对结构体整体的一个倍数处理,已经为结构体分配好45个字节空间,那么接下来就是编译器要求整个结构体所分配的空间大小是结构体中占用空间最多的类型所占用空间大小的倍数。
如上面的例子,结构体中占用空间最多的就是8个字节的数据类型,那么就要求结构体总大小是这个类型的倍数,在这里也就是要求结构体所占用空间大小要是8 的倍数,因为45不是8的倍数,所以结构体被迫扩展自己的空间,以满足需要,所以就扩展到48个字节。
所以sizeof(ST_DATA)的结果是48,就是这样计算出来的。
';
C++读取配置文件
最后更新于:2022-04-01 20:13:16
想项目开发中终归会用到配置文件,方便改服务器IP端口这些
这个代码适合Key-Value键值对类型的配置文件,我的配置文件是这样的
~~~
VideoServerAddress = 192.168.0.139
VideoServerPort = 8000
VideoClientPort = 2000
LandServerAddress = 183.61.39.156
LandSserverPort = 6041
~~~
下面的这份代码我也是拷贝的,然后稍微改造了一下,我运行试了试,确实可以读配置文件。
首先是h文件
~~~
/**************************************
*日期: 2014-12-5
*目的: 读取配置文件的信息,以map的形式存入
*要求: 配置文件的格式,以#作为行注释,配置的形式是key = value,中间可有空
格,也可没有空格
***************************************/
#ifndef _GET_CONFIG_H_
#define _GET_CONFIG_H_
#define COMMENT_CHAR '#'
#include
#include
';
前言
最后更新于:2022-04-01 20:13:13
> 原文出处:[C++学习积累](http://blog.csdn.net/column/details/c-accumulate.html)
作者:[dinghqalex](http://blog.csdn.net/dinghqalex)
**本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!**
# C++学习积累
> 在工作中积累C++知识,涵盖基础知识,编译错误,算法等。
';