Zexun Luo

从 Java 到 Qt/C++ 的一些经验总结

Shaka / 2022-03-07


C/C++ 代码编译成可执行程序的过程

Java 代码编译和执行的过程

静态和动态

#include <stdio.h>
#include <stdlib.h>
int main() {
  int m, n;
  int *p, *q;
  scanf("%d%d", &m, &n);
  p = (int*)malloc(sizeof(int) * m);
  q = (int*)malloc(sizeof(int) * n);
  return 0;
}

m、n、p、q 静态分配内存。局部变量,要占多大空间、往哪里放,在编译时就已经确定。 malloc 函数动态分配内存,在堆区分配内存,把地址赋值给 p、q。

进一步:静态和动态 + static 关键字

#include <stdio.h>
#include <stdlib.h>
void hehe() {
  int k=0;
  static int sum = 0;
  k++;
  sum++;
  printf("%d %d\n", k, sum);
}
int main() {
  int *p;
  p = (int*)malloc(sizeof(int));
  hehe();
  hehe();
  return 0;
}

k、sum 静态分配内存。局部变量,它们要占多大空间、往哪里放,在编译时就已经确定。 static 和静态、动态分配内存没关系,static 指 sum 变量的创建和销毁不会随着 hehe() 函数的调用而一次次创建销毁。

C 语言的内存分区

#include <stdio.h>
#include <stdlib.h>
int total = 0;//全局变量->全局/静态区
void hehe() {
  static int he = 0;//静态局部变量,只创建一次,静态区
  he++;
  total++;
}
int main() {
  int k = 3;//局部变量->栈, 3->文本区
  char *str = "Hello";//str->栈  "Hello"->常量区
  int *p = (int*)malloc(sizeof(int));//p->栈  malloc分配控件->堆
  hehe();//函数每次结束,栈空间都会释放,而静态区不会
  free(p);
  return 0;
}

//文本区->放这个文件编译后的二进制码

动态库、静态库

静态库的特点:

动态库的特点:

动态库与模板

//TemplateLib.h  使用动态库
#ifdef	TEST_DLL_EXPORTS
	#define	TEST_API	__declspec(dllexport)
#else
	#define TEST_API	__declspec(dllimport)
#endif
 
 
// 导出模板函数
template<typename T1>
TEST_API	void	fun1(T1);
 
template<typename T1,typename T2>
TEST_API	void	fun2(T1 , T2);
 
 
// 模板类
template<typename T,int size>
class TEST_API	CTest
{
public:
	CTest()		{};
	~CTest()	{};
	T*	GetDataBuff()	{	return m_data;}
private:
	T	m_data[size];
};
// TemplateLib.cpp : 定义 DLL 应用程序的导出函数。
 
#include "stdafx.h"
#include "TemplateLib.h"
 
// 1.利用重载来实例化不同类型的模板,代码量大不说,基本上是重复的代码
// 2.库的设计者不知道用户会传入什么类型,也就是说设计者不可能实例化每一种类型的模板。
TEST_API	void fun1(int var1)		{}
 
TEST_API	void fun1(char var1)		{}
 
template<typename T1,typename T2>
TEST_API	void fun2( T1 var1, T2 var2)	{}
 
 
// 这个名字空间不作为导出使用,唯一作用是用来例化函数模板和类模板.
namespace implement_template_private
{
 
	void	implement_template()
	{
		int		idata = 10;
		char	chr	= 'x';
		float	fdata = 20.f;
		UINT	undata= 9;
		char*	str	=	"hello";
 
		// 这种方式的实例化,代码量比重载方式少许多,但需运行一次该模板函数
		// 也许在某些时候凭空运行这个函数是不合理的。
		fun2(idata,chr);				// int,char
		fun2(undata,str);				// UINT,char*
		fun2<float,char*>(fdata,str);		// float,char* 显示参数
 
		// 导出类的实例化。
		// 1.除了要实例化提供给用户使用的公有成员函数外,这里面还隐含的实例化了构造函数和析构函数.
		// 2.注意这里每一个模板的实例化都是唯一的。
		// 3.假如客户如果在项目中使用了CTest<char,30> impl_obj; 将会连接错误, 模板的参数列表必须完全匹配。
		// 4.假如该模板类非常大,功能非常多,那么实例化工作可以想象是不堪忍受的。
		// 5.库的设计者不知道用户会传入什么类型,也就是说设计者不可能实例化每一种类型的模板。
		CTest<char,20>	impl_obj;
		impl_obj.GetDataBuff();
 
 
		CTest<int,5>	impl_obj2;
		impl_obj.GetDataBuff();
 
	}
 
};

auto(C++ 11)

其核心在于类型推导,也就是让编译器根据等号右边的表达式来决定auto实际代表的类型。C++的auto只涉及到编译期的行为而不是运行期。

如:

 MyBigDataType& func();
 ...
 auto value = func();

因为 auto 会移除表达式类型的引用属性,那么此时以上最后一行的行为就是拷贝构造一MyBigData实例,相信这不是此函数的实现者希望的。

C++ 引用

参数传递:注意值传递和引用传递的区别。

#include <iostream>
using namespace std;

void fun(int &k) {
  k = 2;
}
int main() {
  int a = 1;
  fun(a);
  cout << a << endl;
  return 0;
}

虚函数表

什么是虚函数表?

对于一个类来说,如果类中存在虚函数,那么该类的大小就会多4个字节,然而这4个字节就是一个指针的大小,这个指针指向虚函数表。所以,如果对象存在虚函数,那么编译器就会生成一个指向虚函数表的指针,所有的虚函数都存在于这个表中,虚函数表就可以理解为一个数组,每个单元用来存放虚函数的地址。

虚函数(Virtual Function)是通过一张虚函数表来实现的。简称为V-Table。在这个表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。这样,在有虚函数的类的实例中分配了指向这个表的指针的内存,所以,当用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

新的 for 循环(C++ 11)

int arr[5] = {1, 2, 3, 4, 5};
for (int x : arr) {
  x += 100;
  cout << x << ' ';
}
cout << arr[0];

lambda(C++)

定义一个匿名函数,还可以捕获外部一定范围内的变量。

auto f1 = [](int a, int b) -> int{return a + b; };

auto f2 = [](auto a, auto b) -> auto{return a + b; };

//编译器就根据 return 语句自动推导出返回值类型。
auto f3 = [](auto a, auto b){return a + b; };

int c = 9;
//按值捕获 c 变量,同时不捕获其他变量。
auto f4 = [c](auto a, auto b){return a + b + c; };

//捕获外部作用域中所有变量(按值捕获)
auto f5 = [=](auto a, auto b){return a + b + c; };

cout << f1(1, 2) << endl;

类型转换(C++)

  int k;
  k = 3.14;//在 Java 中会报错,在 C/C++ 编译器中正常(k==3)。

指针?引用(Java)

Java:基本数据类型(int、long等)、引用数据类型(数组、类、接口)。

Student zs = new Student();     //Java,引用
Student *pzs = new Student();   //c++,指针

for 循环(Java)

int[] arr = new int[10];

for (int temp : arr) {
    temp++;
}
for (Student s : ss) {
  s.changeSomething();
}

lamda(Java)

(parameters) -> expression
或
(parameters) ->{ statements; }
// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)

理解封装

构造和析构(C++)

#include <iostream>
using namespace std;
class Note {
public:
  int nid;
  Note(int id) :nid(id) {}
};

class Student {
private:
  int sid;
  string name;
  Note nt;
public:
  Student(int sid, string name, int id) : sid(sid), name(name), nt(id) {//初始化列表,进行初始化构造
    //写在这里的只能是赋值
  }
}

对象的复制(C++)

Java 类-与C++不同

对象的复制(Java)

Array a = new Array();
Array b = a;

其实是引用的复制。

Array b = new Array(a);

对象的复制(返回新对象的引用)

理解“继承”

C++ 的继承和Java 的继承区别

多态-需求是什么

多态:指为不同数据类型的实体提供统一的接口。

是否有需要使用基类指针(引用)指向(引向)派生类对象?

class People {
public:
  void say() {
    cout << "I 'm a person!" << endl;
  }
};

class Student : public People {
public:
  void say() {
    cout << "I'm a student!" << endl;
    }
};

int main() {
  People *zs = new Student();
  zs->say();
  return 0;
}
class People {
public:
  void say() {}
};
class Student : public People {
public:
  void say() {}
};
class Teacher : public People {
public:
  void say() {}
};
void func(People *zs) { zs->say(); }
int main() {
  Student zs;
  Teacher ls;
  func(&zs);
  func(&ls);
  return 0;
}

Qt 隐式共享

QPixmap p1, p2;
p1.load("image.bmp");
p2 = p1;                        // p1 and p2 share data

QPainter paint;
paint.begin(&p2);               // cuts p2 loose from p1
paint.drawText(0,50, "Hi");
paint.end();

QList

Qt 元对象系统

Qt 的元对象系统为对象间通信、运行时类型信息和动态属性系统提供了信号和槽机制。

Qt 跨平台和 Java 跨平台