原文链接:
https://blog.csdn.net/oyoung_2012/article/details/76423140

进入官网下载页面,下载你需要的镜像, 下载链接:

https://www.raspberrypi.org/downloads/raspbian/
树莓派下载页面

如上图所示,上面两个的jessie为带GUI桌面的,下面一个为纯命令行的,可以根据个人爱好选择

如果是windows系统,只需要使用UtralISO之类的软件将镜像文件烧录到SD即可,如果是在Mac OS或者Linux系统,请按照以下步骤:

  1. 打开终端(ubuntu下的快捷键为Ctrl+Alt+T);
  2. 敲命令: df -h, 查看SD卡所在的分区, 如在Mac下我的SD卡分区为/dev/disk5,Ubuntu下我的SD卡分区为/dev/sdc,具体根据个人情况而定,只需要自己能够找到对应分区即可
  3. 使用umount命令卸载分区,Ubuntu下为”sudo umount /dev/sdc1”, macOS下为”diskutil umount /dev/disk5s1”
  4. 使用cd命令进入到你下载的jessie镜像所在目录,如我的下载目录在/home/oyoung/downloads,然后执行命令sudo dd bs=4m if=<你的镜像文件> of=<你的SD对应的块设备>,比如我上面提到的,Ubuntu下的/dev/sdc以及macOS下的/dev/disk5, 然后等待一段时间,具体视你的SD卡写入速度而定,毕竟镜像文件超过1G
  5. 以上步骤完成后,镜像的烧录工作基本完成,剩下的一些需要做的将SD卡插入树莓派并启动系统之后要做的系统设定工作,比如是否开启SSH,是否开启VNC,以及个别情况下需要设置系统启动屏幕分别率,一般触摸屏厂商的文档里面会提到如何设置

修改系统设置, 开机之后,使用HDMI将树莓派接上显示器,然后插入USB鼠标键盘,打开终端,输入sudo raspi-config命令启动系统配置界面,具体配置自己摸索,然后连接上树莓派的无线网,通过ifconfig命令获取到树莓派的ip地址,记下来,后面一定会用到

原文链接:
https://blog.csdn.net/oyoung_2012/article/details/76422589

1. 下载并安装GLFW

这里给个官网链接: http://www.glfw.org/download.html
打开该页面后, 点击Source Package后会启动下载,下载完成后解压到你自己指定的目录
,比如笔者的解压路径为: ~/Downloads/glfw-3.2.1
打开终端,执行以下命令:

1
2
3
~$ cd ~/Downloads/glfw-3.2.1
~$ cmake -Bbuild -H. -DCMAKE_INSTALL_PREFIX=/usr/local
~$ sudo cmake --build build --target install

2. 下载安装GLEW

下载链接: http://glew.sourceforge.net/index.html
打开页面,点击Source后面的ZIP 或者TGZ, 下载源码压缩包并解压
比如笔者解压目录为: ~/Downloads/glew-2.0.0
继续在终端中执行以下命令

1
2
3
~$ cd ~/Downloads/glew-2.0.0
~$ make GLEW_DEST=/usr/local -j4
~$ sudo make install

3. 使用Xcode新建C++ Command Line项目,并在项目的Build Setting中设置User Header Search Paths(添加一个/usr/local/include)和Library Search Paths(添加一个/usr/local/lib)

其中以下的依赖库为必需的:

IOKit.framework
CoreVideo.framework
Cocoa.framework
OpenGL.framework
libglfw3.a
libGLEW.a

5. 在main函数中调用glfwInit(); 编译,看是否报错, 没有报错则表示所需要的库都已经引入,安装环境就搭建好了,接下来你就可以尽情享受openGL带来的乐趣了

原文链接:
https://blog.csdn.net/oyoung_2012/article/details/54631165

1. C#类型与C/C++类型的对应关系

基本数据类型(C# <—> C/C++)

  • System.Int32,int <—> int, long
  • System.Int64,int64 <—> long long, __int64
  • System.Char, char <—> char, byte, unsigned char
  • System.Int16, short <—> short
  • System.UInt32, uint <—> unsigned int, unsigned long
  • System.UInt16, ushort <—> unsigned short, DWORD
  • System.String, string <—> char[], char *, const char *
  • System.IntPtr <—> void *, [Type]*
  • Sytem.Boolean, bool <—> bool, BOOL

2.C/C++函数参数类型与C#类型对应关系

① 基本的数据类型对应关系与1相同,但是也有一些不一样的地方
② const char * 一般作为输入参数, C#直接使用string类型即可(StringBuilder也可以)
③ char *作为输入参数的时候, C#需要使用ref string类型或者StringBuilder类型
④ 结构体指针做输入参数的时候, C#一般使用ref + 对应的结构体类型

3. C/C++结构体与C#结构体的成员类型对应关系

A. 如果C/C++结构体成员类型是基本数据类型, C#中对应的结构体成员类型使用1中的对应关系即可, 如:

C/C++代码

1
2
3
4
5
6
struct Some
{
int number;
char character;
char name[32];
}

C#代码

1
2
3
4
5
6
7
struct Some
{
int number;
char charactor;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=32)]
string name;
}

B. 如果C/C++的结构体成员中包含的成员仍然是结构体(单个,非数组), 则对应的C#结构体只需要同样包含结构体, 如:

C/C++代码

1
2
3
4
5
6
7
8
9
struct A
{
int number;
};
struct B
{
struct A a;
int otherNumber;
};

对应的C#代码

1
2
3
4
5
6
7
8
9
10
public struct A
{
public int number;
}

public struct B
{
public A a;
public int otherNumber;
}

C. 如果C/C++结构体成员中包含数组(长度固定),则C#结构体中也使用数组对应的类型, 如:

C/C++代码

1
2
3
4
5
6
7
8
9
struct A
{
int number;
};
struct B
{
int numbers[10];
struct A alist[5];
};

对应的C#代码

1
2
3
4
5
6
7
8
9
10
11
public struct A 
{
int number;
}
public struct B
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst=10, ArraySubType=UnmanagedType.SysInt)]
public int[] numbers;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=10, ArraySubType=UnmanagedType.Struct)]
public A[] alist;
}

D. 如果C/C++的结构体成员中包含另一个结构体的指针,C#对应的结构体中一律使用IntPtr类型, 如:

C/C++代码

1
2
3
4
5
6
7
8
9
struct A
{
int number;
};
struct B
{
int tag;
struct A *pA;
}

对应的C#代码

1
2
3
4
5
6
7
8
9
public struct A
{
public int number;
}
public struct B
{
public int tag;
public IntPtr pA;
}

然后在使用的时候需要用到Marshal类的StructureToPtr和PtrToStructure方法进行转换
转换方法如下:
(1)结构体转换成IntPtr

1
2
3
4
5
A a = new A();
B b = new B();
IntPtr pA= Marshal.AllocHGlobal(Marshal.SizeOf(a));
Marshal.StructureToPtr(a, pA, false);
b.pA = pA;

(2)IntPtr 转换成结构体

1
2
3
4
5
6
B b = new B();
//...省略中间的操作过程
A aInB = (A)Marshal.PtrToStructure(b.pA, typeof(A))
//或者
A aInB = new A();
Marshal.PtrToStructure(b.pA, aInB);

4. 关于类型转换中的数组长度

  • C/C++中,如果是char类型的数组(一般表示最大长度不超过某个数字的字符串),那么在进行参数传递的时候,如果C#中的string字符串字节数超过了C/C++中的最大字节数,则会发生截断, 如C/C++中, char name[3]; 但是在C#中却使用了string name = “Jack”,实际传到C/C++中, name中的内容为“Ja”,多余的部分没有填充进来

  • C/C++中使用的数组为定长数组,在参数传递过程中,如果C#中使用的数组长度比C/C++中数组长度短,则会发生System.ArgumentException异常,提示“未能封送类型,因为嵌入数组实例的长度与布局中声明的长度不匹配“(C#的string到C/C++的char 数组不会,但是C#char[]到C/C++的char数组会有此异常),因此, 我们要定义相同长度的数组才能正常工作(C#数组长度更大的时候,会忽略多余的部分)

原文链接:
https://blog.csdn.net/oyoung_2012/article/details/54601314

我们都知道C是一门面相过程的语言,在C的世界里是没有面相对象这个概念的,但是C语言为我们提供的神兵利器,仍旧可以让我们使用面相对象的思维方式
在C语言里,我们每做一个操作,都需要写一个函数,但是该函数都是过程化的,但是我们有两种神兵利器,一个叫指针,一个叫结构体
为什么这么说呢?
面相对象的最基本的功能就是对数据的封装,在C语言的世界里,我们有结构体这个法宝,同样可以将数据打包整整体,然后通过指针的方式,将结构体作为参数在函数中进行传递
举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Book {
char name[32]; //书名
char author[32]; //作者名
int total; //总页数
int price;//价格
};
typedef struct Book Book;
//初始化一本书
void book_init(Book *book, const char *name, const char *author, int total, int price)
{
strcpy(book->name, name);
strcpy(book->author, author);
book->total = total;
book->price = price;
}

void book_update_price(Book *book, int newPrice)
{
book->price = price;
}

以上的例子很简单,初始化一本书以及更新书价,在使用的时候,我们只需要像以下方式调用

1
2
3
4
5
6
Book book;
book_init(&book, "WPF 编程宝典", "Matthew MacDonald", 934, 128);

//...

book_update_price(&book, 100); //降价

以上的这些使用的都是面相过程的思维。
所谓面向过程编程,就好比“让某某去做某事”,而面向对象呢,就好比“某某去做某事”。从字面意思来看,面向过程貌似多了个“高级领导”,而面向对象显得更自由。
###我们再变一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct Book
{
char name[32];
char author[32];
int price;
void (*init)(Book *book, const char *name, const char *author, int price);
void (*update_price)(Book *book, int newPrice);
}


//初始化一本书
void book_init(Book *book, const char *name, const char *author, int price)
{
strcpy(book->name, name);
strcpy(book->author, author);
book->total = total;
book->price = price;
}

void book_update_price(Book *book, int newPrice)
{
book->price = price;
}

//创建一本书
void book_create(Book *book)
{
book->init = book_init;
book->update_price = book_update_price;
}

然后你就可以像以下一样使用了

1
2
3
4
5
6
Book book;
book_create(&book);

book.init(&book, "WPF 编程宝典", "Matthew MacDonald", 128);
//...
book.update_price(&book, 100);

使用这种方式来调用,看起来有了一种“书做了某某事”的面向对象的错觉
###C++正是借鉴了这一点,从而产生了Class(类)
当我们定义一个class的时候,我们自己定义的成员函数就使用了我们上面的思想,参考如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Book {
char name[32];
char author[32;
int price;
void init(const char *name, const char *author, int price)
{
strcpy(this->name, name);
strcpy(this->author, author);
this->price = price;
}
void updatePrice(int price)
{
this->price = price;
}
};

我们是用起来显得更简单

1
2
3
4
Book book;
book.init("WPF 编程宝典", "Matthew MacDonald", 128):
//...
book.updatePrice(100);

看起来与我们使用C语言模仿的面向对象是不是很像?
接下来我们注意到一个关键字this, 指的是调用函数的某个对象,谁调用了,this指的就是谁,上面的book调用init和updatePrice的时候,this指的就是book这个对象。

我想你应该明白了,C++的面向对象,就是使用了我们的上述模拟面向对象的思维,然后C++自己将对象本身作为一个隐含的参数传递给了我们的成员函数,当然,这些并不包括C++面向对象中更强大的继承和多态。
到此我觉得你应该明白了C++成员函数与普通函数之间的区别以及其内部的原理。
再看下面代码

1
2
3
4
5
6
7
8
9
class Student
{
public:
Student(const char *name) { strcpy(this->name, name);}
void makeTest() { std::cout<<"我在考试"<<std::endl;}
void appear() { std::cout<<"My name is "<<name<<std::endl;
private:
char name[32];
};

我们在看如下调用

1
2
3
4
Student *pStu;

pStu->makeTest(); // ①
pStu->appear(); // ②

很多人都会想到,pStu是一个未初始化的变量,以上①②两种调用都会导致崩溃。再仔细想想看,真的是这样吗?
比较细心的同学会发现,在某些编译器上运行,我们却可以看到①调用后的打印信息,而运行②以后,程序崩溃掉了,我想细心的你应该知道这是什么原因了。
很简单,我们在①处调用的makeTest()函数,翻译成C语言的形式应该是

1
2
3
4
5
6
void makeTest(Student *this)
{
//打印“我在考试”
}

makeTest(pStu);

在代码里面,pStu虽然没有初始化,但是在上面的C语言版的makeTest中,我们并没有使用到this这个参数,所以即使这个参数指针是一个野指针或者空指针,都不会出现崩溃现象,但是对于②处的调用,使用的appear()函数中使用到了this, 也就是说,此时的this是一个未初始化的指针(我们称为野指针,不是空指针),这个指针指向什么地方我们并不知道,可能是没有权限操作的一块内存,也可能正好是空指针,也可能是已经分配的某个内存

我们再做一个小实验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Test
{
public:
void test()
{
if(this != nullptr) //C++11中引入的nullptr, 表示空指针,感兴趣的可以自己去多多了解C++11
{
std::cout<<"我不是空的"<<std::endl;
} else {
std::cout<<"我是空的"<<std::endl;
}
}
};

int main()
{
Test t;
Test *p = nullptr; //设置初始值,防止使用未初始化指针
p->test();

p = &t;
p->test();
return 0;
}

你能猜出最终的打印信息吗?(

原文链接:
https://blog.csdn.net/oyoung_2012/article/details/54315963

很多时候,会遇到第三方接口中本来需要使用const char *类型参数的时候,却使用了char *类型,以至于我们原本可以使用如someFunction(“some”), 却编译报错(如某康的SDK)

###针对以上的这种问题,我们可以在对第三方SDK进行本地封装的时候对参数类型进行修改,比如:

1
2
3
4
5
//SDK 头文件

void Login(char *ip, unsigned short port, char *userName, char *password); //这里本来应该要使用const char *类型代替char *类型,因为该函数根本不会修改参数的值,但是基于某某程序员的某某原因,却没有这样使用,以至于我们调用该接口时,并不能像以下方式调用

Login("127.0.0.1", 8000, "admin", "admin"); //编译报错
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//我们应该将其进行正确参数类型的封装
class SDK {
public:
static void login(const char *ip, unsigned short port, const char *userName, const char *password);
private:
static char *transform(const char *pChar);
}

void SDK::login(const char *ip, unsigned short port, const char *userName, const char *password) {
char *newIp = transform(ip);
char *newUserName = transform(userName);
char *newPassword = transform(password);
Login(newIp, port, newUserName, newPassword);
}

char * SDK::transform(const char *pChar) {
//方案1,使用const_cast
return const_cast<char *>(pChar);
//方案2,使用联合体
union { char *p; const char *q;} t;
t.q = pChar;
return t.p;
//方案3, 利用整数类型
int address = (int)pChar;
return (char *)address;
}