Type: Default 1000ms 256MiB

【Level 0】结构体与指针

You cannot submit for this problem because the contest is ended. You can click "Open in Problem Set" to view this problem in normal mode.

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;
}

  这样,就进一步简化了产生一个对象的操作,在定义时直接初始化它们变量的值。

  并且这个行为较为灵活,例如这里初始化了 xy 的值,也可以另外定义函数实现初始化 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;
}

  此时,我们可以得到相同的结果,但请注意, p1p2 指向的是两块 不同的内存空间,这是因为每一次 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;
}

  看起来似乎没有问题,我们正确地输出了 23,但问题在于,既然 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++ 编程解决实际问题时请不要这样使用指针)

C++入门

Not Attended
Status
Done
Rule
IOI
Problem
16
Start at
2024-9-3 0:00
End at
2024-11-25 8:00
Duration
2000 hour(s)
Host
Partic.
112