C++面向对象..

news2024/12/23 5:02:11

1.面向对象的常见知识点

类、
对象、
成员变量(属性)、成员函数(方法)、
封装、继承、多态

2.类

在C++中可以通过struct、class定义一个类
struct和class的区别:
struct的默认权限是public(在C语言中struct内部是不可以定义函数的) 而class的默认权限是private(该权限限制了用class定义的类中的成员只可以在当前类中进行访问 不可以在当前源文件中除了类以外的其他地方访问)
虽说C语言的struct中不可以直接定义函数 但是可以间接定义 通过函数指针的方式指向一个函数 从而达到间接定义函数的目的
以下是C语言中模拟的通过struct定义的类

void test() {
	printf("hello world\n");
}
struct Person {
	int age;
	void(*run)();
};
int main() {
	struct Person p;
	p.age = 10;
	p.run = test;
	p.run();
	getchar();
	return 0;
}

但是在C++中 其实struct也可以用来定义结构体 那么struct到底是叫做结构体还是类 其实并不需要去纠结这个问题 因为这属于语法糖层面的问题
以下是C++中通过struct定义的类 而且在C++中 Person p就是在定义一个对象 只不过这个对象是存放在栈空间中 有别于Java中对象存放在堆空间的事实

struct Person {
	int m_age;
	void run() {
		cout << "Person::run() - " << m_age << endl;
	}
};
int main() {
	Person p;
	p.m_age = 10;
	getchar();
	return 0;
}

以下则是C++中通过class定义的类

class Person {
public:
	int m_age;
	void test() {
		cout << "Person::test() - " << m_age << endl;
	}
};
int main() {
	Person p;
	p.m_age = 30;
	p.test();
	getchar();
	return 0;
}

以上案例都是通过类变量直接访问类对象中的成员 我们也可以通过指针来间接访问类对象中的成员 以下是详细案例

class Person {
public:
	int m_age;
	void test() {
		cout << "Person::test() - " << m_age << endl;
	}
};
int main() {
	Person person;
	Person* p = &person;
	p->m_age = 11;
	p->test();
	getchar();
	return 0;
}

上述的person对象和p指针这些局部变量都是在函数的栈空间中自动分配和回收的 其中对于person对象来说 他的大小等价于m_age的大小
在这里插入图片描述

struct和class之间除了上述说的这个访问权限的区别之外 我们还可以利用反汇编去看一下是否存在其他的区别
以下是通过class定义类的代码以及汇编

class Car {
public:
	int m_price;
};
int main() {
	Car car;
	car.m_price = 10;
	getchar();
	return 0;
}

在这里插入图片描述
以下是通过struct定义的代码和汇编

struct Car {
	int m_price;
};
int main() {
	Car car;
	car.m_price = 10;
	getchar();
	return 0;
}

在这里插入图片描述
对比一下两者的汇编 可以发现 其实两者的底层是一模一样的 他们的唯一差别在于:class的默认权限是private 而struct的默认权限是public

对于以上的分析 可能会有这样的疑问 为什么对象的内存中只储存了成员变量的内存 而没有储存成员函数的内存呢
首先对于成员变量来说 肯定是每一个对象都有一块分配给成员变量的内存 因为每一个对象的属性都各不相同
但是对于成员函数来说 每一个对象共用一块成员函数的内存就够了(每一个对象调用函数所执行的代码相似甚至一样) 而且这块分配给成员函数的内存并不储存在对象中 而是储存在其他地方
至于更深层的原因 等到后面就会逐一解释的
以下是对于每个对象中都有一块分配给成员变量的内存 而每个对象都共用一块分配给成员函数的内存说法的检验

struct Car {
	int m_price;
	void run() {
		cout << "Car::run() - " << m_price << endl;
	}
};
int main() {
	Car car1;
	car1.m_price = 10;
	car1.run();
	Car car2;
	car2.m_price = 20;
	car2.run();
	Car car3;
	car3.m_price = 30;
	car3.run();
	getchar();
	return 0;
}

在这里插入图片描述
我们可以看到 三个对象中的三个成员变量的地址值是不一样的 说明每个对象中都有各自独立的成员变量 而三个对象所调用的成员函数的地址值确实一样的 也正说明了这三个对象共用同一个成员函数
在这里插入图片描述
而且有个细节 就是他将10赋值给了car1所在内存块中 说明了car1的地址值等于car1当中的m_price的地址值

在实际开发中 用class定义类是比较常见的

3.对象中的内存布局

struct Person {
	int m_id;
	int m_age;
	int m_height;
	void display() {
		cout << "m_id" << m_id << ", m_age = " << m_age << ", m_height = " << m_height << endl;
	}
};
int main() {
	Person p;
	p.m_id = 1;
	p.m_age = 10;
	p.m_height = 170;
	cout << "&p = " << &p << endl;// &p = 00F3F978
	cout << "&p.m_id = " << &p.m_id << endl;// &p.m_id = 00F3F978
	cout << "&p.m_age = " << &p.m_age << endl;// &p.m_age = 00F3F97C
	cout << "&p.m_height = " << &p.m_height << endl;// &p.m_height = 00F3F980
	getchar();
	return 0;
}

从打印结果来看 可以发现在对象中定义的多个成员变量的内存是连续分布的 并且是按照成员变量的定义顺序依次排布的

从内存这一方面我们也可以验证

struct Person {
	int m_id;
	int m_age;
	int m_height;
	void display() {
		cout << "m_id" << m_id << ", m_age = " << m_age << ", m_height = " << m_height << endl;
	}
};
int main() {
	Person p;
	p.m_id = 1;
	p.m_age = 10;
	p.m_height = 170;
	cout << "&p = " << &p << endl;// &p = 00F9FD24
	cout << "&p.m_id = " << &p.m_id << endl;
	cout << "&p.m_age = " << &p.m_age << endl;
	cout << "&p.m_height = " << &p.m_height << endl;
	getchar();
	return 0;
}

在这里插入图片描述
从内存中 我们可以发现 定义在对象中的多个成员变量是连续排布并且是按顺序排布的 也进一步证明了对象中只储存了成员变量的内存 并没有储存成员函数的内存

在上述例子中 person对象的内存是储存在函数的栈空间中 因此他可以自动的创建和销毁的 但是在Java中 我们知道对象的创建和销毁都是发生在堆空间中的

但是对于对象中存在不同类型的成员变量 可能会发生内存对齐现象 也就是类对齐 即一个类的大小也为其内部最大成员变量大小的倍数
对于下面这段代码来说 他就发生了内存对齐

class Person {
public:
	int m_age;
	long m_height;
	short m_weight;
};
int main() {
	Person p;
	p.m_age = 10;
	p.m_height = 20;
	p.m_weight = 30;
	cout << "&p = " << &p << endl;
	cout << sizeof(p) << endl;// 12
	getchar();
	return 0;
}

本来sizeof§的结果应该为10 可实际打印的结果却是12 原因在于他触发了类对齐 10显然不是最大成员字节数4的倍数 所以必须得凑到12 方可为4的倍数 所以实际上往原本的10个字节内存中又填充了2个字节 形成了最终的12个字节

4.this

既然我们之前提到了每一个对象中都有一块各自独有的成员变量内存 每一个对象共用一块独立的成员函数内存 那么对于以下这段代码来说 是如何做到不同的对象通过同一份内存去访问不同的成员的呢

struct Person {
	int m_age;
	void run() {
		cout << "Person::run() -- " << m_age << endl;
	}
};
int main() {
	Person p1;
	p1.m_age = 10;
	p1.run();// Person::run() -- 10	
	Person p2;
	p2.m_age = 20;
	p2.run();// Person::run() -- 20
	getchar();
	return 0;
}

我们从打印结果可以看到 这两个不同的对象调用同一份函数内存竟然可以做到打印不同的结果 其实这取决于this指针的存在
在代码区中的成员函数只有获取了位于栈空间中的成员变量的地址值才能够访问其内存 从而取出里面的值 而this指针正好提供了栈空间中成员变量的地址值
this指针本质上是一个指向当前函数调用者的指针 而他会作为对象中成员函数的隐式参数存在 所以在成员函数中 是可以通过this指针间接访问当前调用者对象中的成员的

1.反汇编窥探this

struct Person {
	int m_age;
	void run() {
		m_age = 20;
	}
};
int main() {
	Person p1;
	p1.m_age = 10;
	p1.run();// Person::run() -- 10	
	getchar();
	return 0;
}

我们可以借助excel辅助我们完整的走一趟这段代码背后的汇编语句
在这里插入图片描述
在这里插入图片描述
执行call命令
在这里插入图片描述
执行push ebp 将ebp的指向压栈
在这里插入图片描述
执行mov ebp, esp 将ebp的指向改成esp的指向
在这里插入图片描述
执行sub esp, xxx 将esp往低地址方向移动 移动的量为分配给run函数的栈空间
在这里插入图片描述
执行push ebx、push esi、push edi 将这三个寄存器依次压栈 这是为了防止等会的操作修改了这三个寄存器的值而无法还原
在这里插入图片描述
执行push ecx 将p1 即将函数调用者的地址值压栈
在这里插入图片描述
执行pop ecx 弹栈 并且将栈顶元素赋值给ecx
在这里插入图片描述
执行mov dword ptr [ebp - 8], ecx、mov eax, dword ptr [ebp - 8] 将p1的地址值传递给eax
执行mov dword ptr [eax], 20 将20传递给eax寄存器中的地址值所引导的内存
执行pop edi、pop esi、pop ebx 将栈顶的三个值弹出 并且依次赋值给三个寄存器
在这里插入图片描述
执行add esp, xxx 增量和刚才sub的减量是一致的 相当于回收刚才分配给run函数的栈空间
在这里插入图片描述
执行mov esp, ebp 将esp的指向改成ebp的指向
执行pop ebp 弹栈 并且将栈顶元素赋值给ebp
在这里插入图片描述
执行ret语句 首先会弹栈 其次会跳转到刚才弹出的栈顶元素
在这里插入图片描述
最后调用完毕 也维持了栈平衡

其中有关于this指针的几句核心代码为:

mov         dword ptr [ebp-0Ch],0Ah
lea         ecx,[ebp-0Ch]
mov         dword ptr [ebp-8],ecx
mov         eax,dword ptr [ebp-8]
mov         dword ptr [eax],14h

分析一下上述几句汇编代码
首先将10赋值给了ebp - 0ch 很显然这个ebp - 0ch是p1/p1.m_age的地址值
接着将ebp - 0ch直接赋值给ecx 这样ecx中储存的便是p1的地址值
接着将p1的地址值赋值给ebp - 8 显然ebp - 8是this指针的地址值
接着将ebp - 8中的内容赋值给了eax eax中储存的便是p1的地址值
最后将20赋值给了eax中地址值所引导的内存 也就是p1.m_age = 20

总之 this是一个指向当前函数调用者的指针

5.指针访问成员的本质

我们先来看一看通过对象直接访问成员的本质

struct Person {
	int m_id;
	int m_age;
	int m_height;
	void display() {
		cout << "m_id = " << m_id << ", m_age = " << m_age << ", m_height = " << m_height << endl;
	}
};
int main() {
	Person p;
	p.m_id = 10;
	p.m_age = 20;
	p.m_height = 30;
	p.display();
	getchar();
	return 0;
}

其本质就是

 mov         dword ptr [ebp-14h],0Ah  
 mov         dword ptr [ebp-10h],14h  
 mov         dword ptr [ebp-0Ch],1Eh

我们大致也可以知道ebp - 14h是p的地址值 也是p.m_id的地址值
第一句话中ebp - 14h是p.m_id的地址值 相当于往p.m_id的空间中存放10
第二句话中的ebp - 10h和ebp - 14h相差4个字节 是p.m_age的地址值 他和p.m_id是相邻的 相当于往里面存放20
第三句话的ebp - 0ch和ebp - 10h也是相差4个字节 是p.m_height的地址值 相当于往其中存放了30

再来看一下通过指针间接访问成员的本质究竟是怎样的

struct Person {
	int m_id;
	int m_age;
	int m_height;
	void display() {
		cout << "m_id = " << m_id << ", m_age = " << m_age << ", m_height = " << m_height << endl;
	}
};
int main() {
	Person person;
	Person* p = &person;
	p->m_id = 10;
	p->m_age = 20;
	p->m_height = 30;
	p->display();
	getchar();
	return 0;
}

他的核心汇编如下所示

// Person* p = &person;
// 首先将person对象的地址值赋值给了eax
lea         eax,[ebp-14h]  
// 其次将person对象的地址值通过eax赋值给了ebp - 20h这块内存 ebp - 20h就是p的地址值
mov         dword ptr [ebp-20h],eax  
// p->m_id = 10;
// 然后将person对象的地址值通过ebp - 20h赋值给了eax
mov         eax,dword ptr [ebp-20h]  
// 然后将10赋值给了eax中的person对象所引导的4个字节的内存 即m_id这块内存
mov         dword ptr [eax],0Ah  
// p->m_age = 20;
// 然后将person对象的地址值通过ebp - 20h赋值给了eax
mov         eax,dword ptr [ebp-20h]  
// 首先通过m_age和m_id之间的偏移量获取m_age的地址值 接着将20赋值给了结果地址值所引导的4个字节的内存 即m_age这块内存
mov         dword ptr [eax+4],14h  
// p->m_height = 30;
// 然后将person对象的地址值通过ebp - 20h赋值给了eax
mov         eax,dword ptr [ebp-20h]  
// 首先通过m_height和m_id之间的偏移量获取m_height的地址值 接着将30赋值给了结果地址值所引导的4个字节的内存 即m_height这块内存
mov         dword ptr [eax+8],1Eh  
mov         ecx,dword ptr [ebp-20h]  
call        00A914C4

总结一下指针间接访问对象的成员的汇编代码:
1.通过指针获取对象的地址值
2.通过偏移量(当前成员和对象地址之间的差)获取当前成员变量的地址值
3.根据成员变量的地址值访问成员变量所在的内存空间

但是对比了一下通过对象访问成员和通过指针访问成员的效率 从汇编指令的条数上来看 的确是通过对象访问成员的效率高
但是要知道一点 指针的出现并不是为了比对象访问效率高而诞生的 而是为了针对某些必须用到指针的情景而应运而生的

1.指针间接访问对象成员的有关思考题

以下代码中 实际结果会不会符合预期呢

struct Person {
	int m_id;
	int m_age;
	int m_height;
	void display() {
		cout << "m_id = " << m_id << ", m_age = " << m_age << ", m_height = " << m_height << endl;
	}
};
int main() {
	Person person;
	person.m_id = 10;
	person.m_age = 20;
	person.m_height = 30;
	// 这边之所以需要进行强制转换的操作 原因在于&person.m_age取出来的是int类型数据的地址值 所以返回值应该是int* 和Person*不匹配
	Person* p = (Person*) & person.m_age;
	p->m_id = 40;
	p->m_age = 50;
	p->display();
	getchar();
	return 0;
}

对于这道思考题的结果 我们的预期可能是40 50 30 可实际上的结果是10 40 50
这就和我们刚才所讲到的通过指针间接访问成员中的偏移量有着莫大的关系了
我们刚才说过 通过指针间接访问对象成员的本质就是:通过指针获取对象地址 然后通过偏移量获取当前成员的地址值 最后通过这个地址值访问当前成员所在的储存空间
套在这道题上就是
我们通过指针获取到的地址值是person.m_age的地址值 是&person + 4
然后通过偏移量获取当前成员的地址值 也就是要获取person.m_id的地址值 也就是&person + 4 + 0 其实就是person.m_age的地址值 这和我们所想获取的person.m_id的地址值大相径庭 当然赋值的时候也就出现了差错
之后的p->m_age也是同理

以下代码中 通过对象访问的成员函数和通过指针访问的成员函数所打印的结果是否一致

struct Person {
	int m_id;
	int m_age;
	int m_height;
	void display() {
		cout << "m_id = " << m_id << ", m_age = " << m_age << ", m_height = " << m_height << endl;
	}
};
int main() {
	Person person;
	person.m_id = 10;
	person.m_age = 20;
	person.m_height = 30;
	Person* p = (Person*) & person.m_age;
	p->m_id = 40;
	p->m_age = 50;
	// 以下两种写法访问成员函数时打印的结果是否一致
	person.display();
	p->display();
	getchar();
	return 0;
}

答案是不一致
我们其实得知道成员函数中访问成员变量的本质 就是通过this指针间接访问对象中的成员变量 因此他在访问某一成员的时候 也遵循"先通过指针获取对象地址 在通过偏移量获取成员地址 最后通过成员地址访问成员所在的储存空间"的原则
按照上述这个原则 对于对象直接访问的结果是肯定符合预期的 其中this指针是指向person对象的
但是对于指针间接访问的话 那么this指针储存的就是p指针中储存的地址值 即person.m_age的地址值 那么到时候在访问的过程中 比如访问person.m_id的时候 偏移量就是0 那么获取到的值是person.m_age的值 而不是person.m_id的值 同理打印person.m_age的时候 结果为person.m_height的值(&person + 4 + 4) 再者 打印person.m_height的时候访问到的内存是一块未知的内存(&person + 4 + 4 + 4) 从而打印出乱码来

我们可以看到 这段乱码是0xcccccccc
在这里插入图片描述
那么为什么要用一段cc来填充栈空间呢?原因在于当我们为一个函数分配一个新的栈空间时 这段空间中可能会有之前残留下来的垃圾数据 所以用cccc这种数据去填充整段分配的内存
那么为什么一定要选择cc作为填充的数据呢?原因在于之前残留的数据中可能存在某些危险的指令 万一我通过指针指向跳转指令 跳转到函数的栈空间中的某个危险代码 那么后果将不堪设想
而cc是int3的意思 int是interrupt的简称 即中断的意思 3是中断码 表示为断点的意思 我们都清楚 代码只要执行到断点位置处的话 那么就会停止执行

我们可以在汇编和内存中分析一下0xcccccccc

struct Person {
	int m_id;
	int m_age;
	int m_height;
	void display() {
		cout << "m_id = " << m_id << ", m_age = " << m_age << ", m_height = " << m_height << endl;
	}
};
int main() {
	Person person;
	person.m_id = 10;
	person.m_age = 20;
	person.m_height = 30;
	Person* p = (Person*) & person.m_age;
	p->m_id = 40;
	p->m_age = 50;
	// 以下两种写法访问成员函数时打印的结果是否一致
	person.display();
	p->display();
	getchar();
	return 0;
}

还是刚才这段代码 但是我们进去display函数中分析分析
在这里插入图片描述
可以看到 在使用cc填充栈空间之前 栈空间还是一堆垃圾数据
当我们执行了mov eax, 0xcccc 以及 rep stos指令以后 再看一下内存中的效果
可以看到整个栈空间都被0xcccc所填满

6.函数执行时涉及的内存

我们在调用函数、执行函数代码的过程中 肯定会涉及到内存 一方面是用于储存函数代码的代码区 一方面是用于储存函数内部局部变量的栈空间 这两个究竟怎么区分呢?
其实不难 在调用函数的过程中 cpu会执行代码区中的函数代码 在执行过程中 遇到局部变量 就会在栈空间中为其开辟空间
有人说 为什么不直接在代码区中为局部变量开辟内存空间呢?因为代码区是只读的 而局部变量是可修改的 显然将局部变量放在代码区不合适 所以将其放置在栈空间中

7.C++的编程规范

我们之前学习Java的过程中 是有学习过Java的一套编程规范 也就是对于一些标识符来说 是需要有一些规范的 比如对于类、接口等这些类型的标识符采用大驼峰 对于方法、变量这些非类型的标识符采用小驼峰
而在C++中 也有着自己的一套规范:
变量名命名规范:
全局变量:g_
成员变量:m_
静态变量:s_
常量:c_
也可以使用和Java一样的驼峰标识去表示一个标识符 比如对于全局变量age来说 我们可以命名为gAge

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1508462.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

上位机图像处理和嵌入式模块部署(qmacvisual旋转和镜像)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 旋转和镜像是图像处理中经常遇到的一个情况。很多时候&#xff0c;摄像头面对物体进行拍摄&#xff0c;未必是正对着进行拍摄的&#xff0c;这个时…

谷粒商城【成神路】-【10】——缓存

目录 &#x1f9c2;1.引入缓存的优势 &#x1f953;2.哪些数据适合放入缓存 &#x1f32d;3.使用redis作为缓存组件 &#x1f37f;4.redis存在的问题 &#x1f9c8;5.添加本地锁 &#x1f95e;6.添加分布式锁 &#x1f95a;7.整合redisson作为分布式锁 &#x1f697…

JavaScript实现通过键盘弹钢琴的效果

本片文章通过触发键盘事件来触发对应的音乐&#xff0c;而且给页面添加了渐变的active类名&#xff0c;通过触发不同的鼠标事件&#xff0c;然后active类移动来实现按下钢琴键的视觉效果。 关键代码&#xff1a; <!DOCTYPE html> <html lang"en"><h…

提示并输入一个字符串,统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数要求使用C++风格字符串完成

#include <iostream> #include <array> using namespace std;int main() {cout<<"请输入一个字符串"<<endl;//array<string,100> str;string str;getline(cin,str);int daxie0,xiaoxie0,num0,space0,other0;int lenstr.size();;for(in…

Java随手记

equals和的区别 使用基本数据类型&#xff08;char&#xff0c;int&#xff0c;long等&#xff09;的时候&#xff0c;比较的是它们的值 使用引用数据类型的时候&#xff0c;比较的是地址 equals如果不重写&#xff0c;那么和 是没差别的 下面来看String的比较&#xff0c;这…

Spring Security自定义认证授权过滤器

自定义认证授权过滤器 自定义认证授权过滤器1、SpringSecurity内置认证流程2、自定义Security认证过滤器2.1 自定义认证过滤器2.2 定义获取用户详情服务bean2.3 定义SecurityConfig类2.4 自定义认证流程测试 3、 基于JWT实现无状态认证3.1 认证成功响应JWT实现3.2 SpringSecuri…

Kafka MQ 生产者和消费者

Kafka MQ 生产者和消费者 Kafka 的客户端就是 Kafka 系统的用户&#xff0c;它们被分为两种基本类型:生产者和消费者。除 此之外&#xff0c;还有其他高级客户端 API——用于数据集成的 Kafka Connect API 和用于流式处理 的 Kafka Streams。这些高级客户端 API 使用生产者和消…

如何保证消息的可靠传输

数据的丢失问题&#xff0c;可能出现在生产者、MQ、消费者中 生产者丢失&#xff1a; 生产者将数据发送到 RabbitMQ 的时候&#xff0c;可能数据就在半路给搞丢了&#xff0c;因为网络问题啥的&#xff0c;都有可能。此时可以选择用 RabbitMQ 提供的事务功能&#xff0c;就是生…

脚手架cli快速创建Vue2/Vue3项目

前言&#xff1a; 本文的nodejs版本是14.21.3 第一步 进入cmd窗口 1、全局安装webpack npm install webpack-g&#xff0c; npm install webpack-g 第二步 2、全局安装vue脚手架 npm install -g vue/cli 第三步 3、初始化vue项目 &#xff08;vue脚手架使用webpack模…

资料下载-嵌入式 Linux 入门

学习的第一步是去下载资料。 1. 有哪些资料 所有资料分 4 类&#xff1a; ① 开发板配套资料(原理图、虚拟机的映像文件、烧写工具等)&#xff0c;放在百度网盘 ② 录制视频过程中编写的文档、源码、图片&#xff0c;放在 GIT 仓库 ③ u-boot、linux 内核、buildroot 等比较大…

STM32CubeMX学习笔记18——FSMC(TFT-LCD屏触摸)

1.触摸屏简介 目前最常用的触摸屏有两种&#xff1a;电阻式触摸屏和电容式触摸屏 1.1 电阻式触摸屏 电阻式的触摸屏结构如下图示&#xff0c;它主要由表面硬涂层、两个ITO层、间隔点以及玻璃底层构成&#xff0c;这些结构层都是透明的&#xff0c;整个触摸屏覆盖在液晶面板上…

45、C++/基础练习20240311

一、提示并输入一个字符串&#xff0c;统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数 要求 使用C风格字符串完成。 代码&#xff1a; #include <iostream>using namespace std;int main() {string buf;//定义字符串类型变量bufcout << &…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Stack容器组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Stack容器组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Stack容器组件 堆叠容器&#xff0c;子组件按照顺序依次入栈&#xff0c;后一…

软件测试需要学什么?学多久?软件测试技术进阶路线图

很多新手&#xff0c;不知道软件测试学习该如何开始&#xff0c;软件测试需要掌握哪些知识。下面是根据本人的理解&#xff0c;粗略整理的一个学习大纲&#xff0c;基本上涵盖了软件测试工程师需要掌握的全部技能&#xff0c;希望对刚入行或者准备学习测试的朋友提供一点指引。…

哈希表|1.两数之和

力扣题目链接 /*** Note: The returned array must be malloced, assume caller calls free().*/// leetcode 支持 ut_hash 函式庫typedef struct {int key;int value;UT_hash_handle hh; // make this structure hashable} map;map* hashMap NULL;void hashMapAdd(int key, i…

Qt教程 — 1.1 Linux下安装Qt

目录 1 下载Qt 1.1 官方下载 1.2 百度网盘下载 1.3 Linux虚拟机终端下载 2 Qt安装 3 安装相关依赖 4 测试安装 1 下载Qt 1.1 官方下载 通过官网下载对应版本&#xff0c;本文选择的版本为qt-opensource-linux-x64-5.12.12&#xff0c;Qt官方下载链接&#xff1a;htt…

【C++庖丁解牛】模拟实现STL的string容器(最后附源码)

&#x1f4d9; 作者简介 &#xff1a;RO-BERRY &#x1f4d7; 学习方向&#xff1a;致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f4d2; 日后方向 : 偏向于CPP开发以及大数据方向&#xff0c;欢迎各位关注&#xff0c;谢谢各位的支持 目录 1.vs和g下string结构…

P5266 【深基17.例6】学籍管理题解

题目 您要设计一个学籍管理系统&#xff0c;最开始学籍数据是空的&#xff0c;然后该系统能够支持下面的操作&#xff08;不超过条&#xff09;&#xff1a; 插入与修改&#xff0c;格式1 NAME SCORE&#xff1a;在系统中插入姓名为NAME(由字母和数字组成不超过20个字符的字符…

如何使用群晖NAS结合cpolar内网穿透实现公网访问本地Office文件

文章目录 本教程解决的问题是&#xff1a;1. 本地环境配置2. 制作本地分享链接3. 制作公网访问链接4. 公网ip地址访问您的分享相册5. 制作固定公网访问链接 本教程解决的问题是&#xff1a; 1.Word&#xff0c;PPT&#xff0c;Excel等重要文件存在本地环境&#xff0c;如何在编…