#CPP8. 【Level 0】结构体与指针
【Level 0】结构体与指针
Description
我们沿用了往年的实验框架,故有些题目需要用到指针操作,但再次提醒,在编写 C++ 程序时,请尽量使用引用,如果一定要用指针,也请使用智能指针。
结构体
结构体可以方便地打包不同类型的变量,C++的结构体定义形式如下
struct Point {
double x, y, val; // 代表点的坐标和价值
};
使用方法也很简单,只需要把结构体定义当作一种新的类型即可。定义后用 .
(点操作符)来获取结构体内的变量
Point p;
cout << "坐标为 (" << p.x << " " << p.y << ")" << endl;
C++ 中可以把函数也定义在结构体内,例如我们将计算两个点的距离的函数放在结构体中(但实际上用类更好):
struct Point {
double x, y, val; // 代表点的坐标和价值
double dis(const Point &p) {
// p为另一个点
return sqrt(pow(x - p.x, 2) + pow(y - p.y, 2));
}
};
int main() {
Point p0, p1;
p0.x = 1, p0.y = 2;
p1.x = 2, p1.y = 3;
cout << "距离为: " << p0.dis(p1) << endl;
}
我们可以方便地计算并输出出距离为 1.41421
指针
指针所指向的是变量的地址,如
int a = 5;
int* p = &a;
(请区别这里的取地址,和函数参数列表中的引用,虽然都是 &
但含义不同)
简单来看,p
也是一个变量,只不过变量的类型是 int*
,它的内容是某个 int
变量的地址。
我们可以使用 *p
来操纵它所指向的内存地址。此时操作 *p
和直接操作 a
没有区别,因为 p
中存储的就是 a
的内存地址。
cout << a << " " << *p << endl;
a = 6;
cout << a << " " << *p << endl;
*p = 7;
cout << a << " " << *p << endl;
我们可以得到
5 5
6 6
7 7
结构体初始化
我们已经知道,结构体内可以定义一些函数,更进一步,我们可以定义构造函数,可以在产生对象时,初始化结构体内变量的值,甚至做其它操作。例如
struct Point {
double x, y, val; // 代表点的坐标和价值
double dis(const Point &p) {
// p为另一个点
return sqrt(pow(x - p.x, 2) + pow(y - p.y, 2));
}
Point(const int &x_, const int &y_) {
x = x_;
y = y_;
val = 0;
}
};
int main() {
Point p0(1, 2), p1(2, 3);
cout << "距离为: " << p0.dis(p1) << endl;
}
这样,就进一步简化了产生一个对象的操作,在定义时直接初始化它们变量的值。
并且这个行为较为灵活,例如这里初始化了 x
和 y
的值,也可以另外定义函数实现初始化 x
y
val
全部的值,或者只初始化 x
。
让我们回顾基础变量的指针定义
int a = 5;
int* p = &a;
既然我们把结构体也看作一种类型,则结构体的指针是类似的定义方式
Point p0(1, 2);
Point* p_p0 = &p0;
此时,操作 *p_p0
与操作 p0
的效果相同,因为 p_p0
中存储的是 p0
的地址。
指针问题1
但需要注意的是,这里有个问题在于 p0
的生命周期,观察下面的操作
Point* foo() {
Point p1(2, 3);
Point* p_p1 = &p1;
return p_p1;
}
int main() {
Point* p1 = foo();
Point* p2 = foo();
cout << (*p1).x << endl;
cout << (*p2).x << endl;
}
表面上看起来,我们获得了两个 Point
变量的指针,似乎它们的值也应该是相同的,但我们运行之后发现并不相同。
这是因为 p1
的作用域问题,它在 foo
函数返回时,生命周期已经结束了,虽然我们返回了它的内存地址,但这时这个地址的空间已经被释放,不再被一个 Point
类型的变量占据,所以如果我们仍视为 Point
变量访问这个地址,就会获得不可预期的结果。
指针问题2
为了解决这个问题,我们可以使用 new
关键字进行实例化,其实与 类型 变量名
的方式是类似的,但注意需要用指针类型来接收:
Point* p_p1 = new Point(2, 3);
这样做的好处在于,new
分配的空间不会被自动释放。这是因为 new
相当于分配了一块空间,但这块空间并不是绑定在某个变量上的,因而并不会随着变量生命结束而被释放(所谓释放就是这块内存不再被你的程序控制)
Point* foo() {
Point* p_p1 = new Point(2, 3);
return p_p1;
}
int main() {
Point* p1 = foo();
Point* p2 = foo();
cout << (*p1).x << endl;
cout << (*p2).x << endl;
}
此时,我们可以得到相同的结果,但请注意, p1
与 p2
指向的是两块 不同的内存空间,这是因为每一次 new
都分配一块新的空间,你可以尝试修改其中一个的值,观察另一个是否发生变化。
-> 运算符
此外,指针有特殊的操作符 ->
,可以直接操作它所指向的结构体内的变量,如 (*p1).x
就等价于 p1->x
到这里你已经可以解决实验题目了,如果你有兴趣我们接下来讨论 new
的问题所在。
指针问题3(了解)
一个老生常谈的话题,使用 C++ 尽可能不要使用裸指针,主要问题在于指针所指向空间的转移与释放的问题。
new
操作申请内存空间虽然解决了之前提到的问题,但又引入了新的问题。首先看一个例子
int main() {
Point* p_p1 = new Point(2, 3);
cout << p_p1->x << endl;
p_p1 = new Point(3, 4);
cout << p_p1->x << endl;
}
看起来似乎没有问题,我们正确地输出了 2
和 3
,但问题在于,既然 new
申请的空间不会被自动释放,那么第一个 Point
的空间现在是什么状态?答案是该空间仍然被我们程序占用,但现在没有手段可以管理它,这就是 内存泄漏 问题。
产生了大量的不可管理的内存后,系统性能会下降,可用的内存会越来越少直至崩溃。
使用 new
申请的空间,需要调用 delete
手动释放,如
int main() {
Point* p_p1 = new Point(2, 3);
cout << p_p1->x << endl;
delete p_p1;
p_p1 = new Point(3, 4);
cout << p_p1->x << endl;
delete p_p1;
}
这样看起来很简单,但程序功能变得复杂后,难以确保 new
分配的空间在合适的时机 delete
,因此,在使用 C++ 时,请尽量使用 引用:&
。
如果实际需求使得一定要使用指针风格,请使用 智能指针,优先使用 unique_ptr
,如果不行再使用 shared_ptr
,有时还需要用到 weak_ptr
。
智能指针的好处在于不需要你手动释放,对于上面的 new
分配空间的问题这一例子,你可以简单理解为,智能指针维护了一个引用计数,当没有任何指针指向这一内存空间时,自动地释放内存空间,而不是将其悬置。
但需要注意,智能指针不能管理裸指针对内存空间的引用,也就是说,如果你混用裸指针和智能指针,很有可能空间被智能指针提前释放,造成不可预知的后果。
题目描述
给出结构体
struct Point {
double x, y;
double dis(const Point &p) {
// p为另一个点
return sqrt(pow(x - p.x, 2) + pow(y - p.y, 2));
}
Point(const int &x_, const int &y_) {
x = x_;
y = y_;
}
};
我们收集了很多个点的数据,现在请你实现一个函数,接受这些点,并按照顺序,找出其中 x
>y
,并且与 (0,0)
的距离大于 3
的点,返回一个 vector<Point*>
,里面是 新分配了内存空间 的 Point
指针。
出于对你的信任,我们会在调用 copyFilter
之后清空传入的 vector
,因此如果你的指针指向传入的 vector
,很可能会发生意料之外的效果。
在 C++入门
中,提到一种遍历 vector
的简单方法
提交
你需要提交的代码如下形式:
#include "Solution.h"
vector<Point*> copyFilter(const vector<Point> &points) {
// 请实现你的代码
}
(再次强调,使用 C++ 编程解决实际问题时请不要这样使用指针)
Related
In following contests: