c/c++零基础坐牢第十五天

c/c++从入门到入土(15)

开始时间2023-05-23 20:44:53

结束时间2023-05-24 00:08:22

前言:目前已复习第一章第二章第三章第六章为c艹基于c语言升华的内容,今天我们进入c艹中一大重要板块第四章 类与对象的学习,同时以读书笔记与体会、作业五 类与对象及实验一的形式分享。对象是有形的也可以是无形的,我的理解int,float,bool都是一种特殊的类,可进行本身属性的操作和关系运算,定义一个类相当于赋予该类的变量一种“特性”,对象就是存储“类”类型的变量。今日学习 第四章 类与对象 参考于清华大学出版社郑莉老师的《C++语言程序设计》第五版,以下为自制思维导图:

 第四章 类与对象

读书笔记与体会

复制代码
 1 面向对象方法中的抽象,是指对具体问题(对象)进行概括,抽出一类对象的公共性质并加以描述的过程。
 2 对一个问题的抽象应该包括两个方面:数据抽象和行为抽象。
 3 前者描述某类对象的属性或状态,也就是此类对象区别于彼类对象的特征。
 4 后者描述的是某类对象的共同行为或功能特征。
 5 我的理解,数据抽象对应这类中的变量,而功能抽象对应该类中的函数。
 6 封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的函数代码进行有机地结合,形成“类”】,其中的数据和函数都是类的成员。
 7 将数据和代码封装为一个可重用的程序模块,在编写程序时就可以有效利用已有的成果。由于通过外部接口,依据特定的访问规则,就可以使用封装好的模块。
 8 然后把特殊与一般的概念间的关系描述清楚,使得特殊概念之间既能共享一般的属性和行为,又能具有特殊的属性和行为呢?
 9 继承就是解决这一问题的。只有继承,才可以在一般概念基础上,派生出特殊概念,使得一般概念中的属性和行为可以被特殊概念共享,摆脱重复分析、重复开发的困境。c艹语言中提供类的继承机制,允许程序员在保持原有类特性就基础上,进行更具体、更详细的说明。
10 包含多态和类形参数化多态属于一般多态性,是真正的多态性。
11 多态性是指一段程序能够处理多种类型对象的能力。
12 在c艹语言中,这种多态性可以通过强制多态、重载多态、类型参数化多态、包含多态4种形式来实现。
面向对象程序设计的基本特点
复制代码
复制代码
 1 在面向对象程序设计中,程序模块是由类构成的。
 2 类是对逻辑上相关的函数与数据的封装,它是对问题的抽象描述。
 3 通过对现实世界的对象进行数据抽象和功能(行为抽象),得到类的数据成员和函数成员。
 4 定义类的语法形式如下:
 5 class 类的名称
 6 {
 7     public:
 8         外部接口;
 9     protected:
10         保护型成员;
11     private
12         私有成员;
13 };
14 我们可以为数据成员提供一个类内初始值(默认形参值),在创建对象时,类内初始值用于初始化数据成员,没有初始值的成员将被默认初始化。
15 访问控制属性可以有以下三种:公有类型(public)、私有类型(private)和保护类型(protected)。
16 公有类型成员定义了类的外部接口。
17 私有成员只能被本类的成员函数访问,来自类外部的任何访问都是非法的。
18 习惯 一般情况下,一个类的数据成员都应该声明为私有成员,这样,内部数据结构就不会对该类以外的其余部分造成影响,程序模块之间相互作用就被降低到最小。
19 保护类型成员的性质和私有成员的性质相似,其差别在于继承过程中对产生的新类影响不同。
20 类实际上是一种抽象实例,它描述了一类事物的共同属性和行为。
21 类的对象就是该类的某一特定实体。
22 对象所占据的内存空间只是用于存放数据成员,函数成员不在每个对象中存储副本,每个函数的代码在内存中只占据一份空间。
23 调用一个成员函数与调用普通函数的差异在于,需要使用“.”操作符指出调用所针对的对象,这一对象在本次调用中称为目的对象。
24 在类的成员函数中,既可以访问目的对象的私有成员,又可以访问当前类的其他对象的私有成员。
25 类成员函数的默认值,一定要写在类定义中,而不能写在类定义之外的函数中。
26 如果有的函数成员需要被频繁调用,而且代码比较简单的,这个函数也可以定义为内联函数。
27 内联函数的声明有两种方式:隐式声明和显式声明。
28 将函数体直接放在类体内,这种方法称之为隐式声明。
29 为了保证类定义的简洁,可以采用关键字inline显式声明的方式。即在函数体实现时,在函数返回值类型前加上inline;类定义中不加人函数体。
类与对象
复制代码
复制代码
 1 构造函数的作用就是在对象被创建时利用特定的值构造对象,将对象初始化为一个特定的状态。
 2 构造函数在对象被创建的时候将被自动调用。
 3 调用时无须提供参数的构造函数称为默认构造函数。
 4 对于一个普通的类来说,通常要定义自己的默认构造函数,原因有三:
 5 第一由于编译器只在类不包含任何构造函数的情况下才会替我们生成默认构造函数;
 6 第二是合成的默认构造函数可能会执行错误的操作;
 7 第三是有时候编译器不能为某些类合成默认的构造函数。
 8 复制构造函数是一种特殊的构造函数,具有一般构造函数的所有特性,其形参是本类的对象的引用。其作用是使用一个已经存在的对象(由复制构造函数的参数指定),去初始化同类的一个新对象。
 9 隐含的复制构造函数的功能是,把初始值对象的每个数据成员的值都复制到新建立的对象中。
10 复制构造函数在以下三种情况下都会被调用:
111)当用类的一个对象去初始化该类的另一个对象时;
122)如果函数的形参是类的对象,调用函数时,进行形参和实参结合时;
133)如果函数的返回值是类的对象,函数执行完成返回调用者时。
14 析构函数与构造函数的作用几乎正好相反,它用来完成对象被删除前的一些清理工作。
15 析构函数是在对象的生存期即将结束的时刻被自动调用的。
16 与构造函数不同的是,析构哈数不接受任何参数,但可以是虚函数。
17 左值是位于赋值语句左侧的对象变量,右值是赋值语句右侧的值,不依附于对象。
18 对持久存在变量的引用,称之为左值引用,相对的对短暂存在可被移动的右值的引用称之为右值引用。
19 default和delete两个关键字来简化构造函数的定义与使用。
20 使用=default可显示要求编译器自动生成默认或复制构造函数。
21 通过使用default,可以让编译器合成简单的无参默认构造函数和复制构造函数,但其他使用参数的构造函数,由于编译器不知构造逻辑,需要用户自行定义。当用户不希望定义的类存在复制时,可以通过delete关键字将复制狗砸函数删除。
22 与default使用不同的是,delete不限于在无参和复制构造函数上使用,除析构函数外,用户都可以指定为delete删除掉,以便禁止类使用过程中的相关操作。
构造函数与析构函数
复制代码
复制代码
1 类的组合描述的就是一个类内嵌其他类的对象的情况,它们之间的关系是一种包含与被包含的关系。
2 当创建类的对象时,如果这个类具有内嵌对象成员,那么各个内嵌对象将被自动创建。
3 在创建对象时既要对本类的基本类型数据成员进行初始化,又要对内嵌对象成员进行初始化。
4 析构该函数的调用执行顺序与构造函数刚好相反。析构函数的函数体被执行完毕后,内嵌对象的析构函数被一一执行,这些内嵌对象的析构函数调用顺序与它们在组合类的定义中出现的次序刚好相反。
类的组合
复制代码

结构体与联合体基本内容详见坐牢第十三天

1 结构体和类的唯一区别在于,结构体和类具有不同的默认访问控制属性:在类中,对于未指定访问控制属性的成员,其访问控制属性为私有类型;在结构体中,对于未指定任何访问控制属性的成员,其访问控制属性为公有类型。
结构体与联合体

enum枚举类型基本内容详见坐牢第十三天 

作业五 类与对象

下列函数中,( )不能重载。
A.成员函数

B.非成员函数

C.析构函数

D.构造函数

答案:C

有关类和对象的说法下列不正确的有( )。
A.对象是类的一个实例

B.任何一个对象只能属于一个具体的类

C.一个类只能有一个对象

D.类与对象和关系与数据类型和变量的关系相似

 答案:C

复制代码
在下面类声明中,关于生成对象不正确的是( )。
class point
{ public:
int x;
int y;
point(int a,int b) {x=a;y=b;}
};

A.point p(10,2);

B.point *p=new point(1,2);

C.point *p=new point[2];

D.point *p[2]={new point(1,2), new point(3,4)};
复制代码

答案:C

以下说法中正确的是
A.一个类一定会有无参构造函数

B.构造函数的返回值类型是void

C.一个类只能定义一个析构函数,但可以定义多个构造函数

D.一个类只能定义一个构造函数,但可以定义多个析构函数

答案:C

函数题

复制代码
6-1 统计数字
分数 10
作者 李廷元
单位 中国民用航空飞行学院
对于给定的一个字符串,统计其中数字字符出现的次数。

类和函数接口定义:
设计一个类Solution,其中包含一个成员函数count_digits,其功能是统计传入的string类型参数中数字字符的个数并返回。
裁判测试程序样例:
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <string>
#include <iostream>
using namespace std;

/* 请在这里填写答案 */
 
int main()
{
    int t;
 
    cin >> t;
    getchar();
    while (t--)
    {
        string str;
        Solution obj;

        getline(cin,str);
        int digits = obj.count_digits(str);
        
        cout << digits << endl;
    }
 
    return 0;
}


输入样例:
2
asdfasdf123123asdfasdf
asdf111111111asdfasdfasdf
输出样例:
6
9
代码长度限制
16 KB
时间限制
400 ms
内存限制
64 MB
问题描述
复制代码
复制代码
 class Solution{
    public:int count_digits(string str);
};
       int Solution::count_digits(string str){
        int n=0;
        int i=0;
        for(i=0;i<str.length();i++){
            if(str[i]>='0'&&'9'>=str[i])
            {n+=1;}
        }
        return n;
    }
复制代码

实验一 类与对象

一、定义一个圆类Circle

复制代码
6-1 【CPP0003】定义一个圆类Circle
分数 10
作者 C++类与对象
单位 石家庄铁道大学
定义一个圆类Circle,main()函数完成对其的测试。

###Circle 类结构说明:

Circle类的数据成员包括:
①静态数据常量PI(double型),其值为3.1415
②私有数据成员:半径radius(double型)。

Circle类成员函数包括:
①定义有参构造函数Circle(double)和拷贝构造函数Circle(Circle &),其中有参构造函数参数默认值为0,输出信息“Constructor run”,拷贝构造函数输出信息“CopyConstructor run”
②定义析构函数,析构函数输出信息“Destructor run”
③公有函数成员:void  setRadius(double)和double  getRadius()分别设置和返回radius
④定义公有成员函数double  circumference()用于计算圆的周长
⑤定义公有成员函数double  area()用于计算圆的面积
⑥定义Circle类的友元函数double  fCircle_L(Circle  &r)和double  fCircle_S(Circle &r),分别用于计算圆r的周长和面积。
裁判测试程序样例:
#include<iostream>
using namespace std;

/* 请在这里填写答案 */

int main(void){
    double  r;
    cin >> r;
    Circle  c1;
    c1.setRadius(r);
    cout << c1.circumference() << endl;
    cout << c1.area() << endl;
    Circle  c2(c1);
    cout << fCircle_L(c2) << endl;
    cout << fCircle_S(c2) << endl;
    return 0;
}
输入样例:
1.0
输出样例:
Constructor run
6.283
3.1415
CopyConstructor run
6.283
3.1415
Destructor run
Destructor run
代码长度限制
16 KB
时间限制
400 ms
内存限制
64 MB
问题描述1
复制代码
复制代码
class Circle{
    public:
    Circle(double a=0);
    Circle(const Circle &obj);
    ~Circle();
    void setRadius(double);
    double  getRadius();
    double  circumference();
    double  area();
    friend double  fCircle_L(Circle &r);
    friend double  fCircle_S(Circle &r);
    private:
    double radius;
    double PI=3.1415;

};
Circle::Circle(double)
{
    cout<<"Constructor run"<<endl;
}
Circle::Circle(const Circle &obj){
    radius=obj.radius;
    cout<<"CopyConstructor run"<<endl;
}
Circle::~Circle(void){
    cout<<"Destructor run"<<endl;
}
void Circle::setRadius(double r)
{
    radius=r;
}
double  Circle::getRadius(void){
    return radius;
}
double  Circle::circumference(){
    double c;
    c=radius*PI*2;
    return c;
}
double  Circle::area(){
    double s;
    s=radius*radius*PI;
    return s;
}
double  fCircle_L(Circle &r)
{
    return r.Circle::circumference();
}
double  fCircle_S(Circle &r)
{
    
    return r.Circle::area();
}
源代码1
复制代码

二、定义学生类Student

复制代码
定义一个学生类Student,main()函数完成对其的测试。

###Student类结构说明:

Student类的数据成员包括:
①私有数据成员:学号no(char[]型),姓名name(char[]型),年龄age(int型)。
②静态数据成员:sum(int型),作用为统计当前时刻Student类对象的总数。

Student类成员函数包括:
①定义有参构造函数Student(char *,char *,int)和拷贝构造函数Student(Student &),其中有参构造函数参数默认值为空串或0(当字符串参数为NULL时视为空串处理),输出信息“Constructor run”,拷贝构造函数输出信息“CopyConstructor run”
②定义析构函数,析构函数输出信息“Destructor run”
③公有函数成员:void  setNo(char *)和char*  getNo()分别设置和返回no(当参数为NULL时视为空串处理)
④公有函数成员:void  setName(char* )和char*  getName()分别设置和返回name(当参数为NULL时视为空串处理)
⑤公有函数成员:void  setAge(int)和int getAge()分别设置和返回age
⑥公有函数成员:void  show()用于显示当前对象信息age。假定“学号=20190327,姓名=doublebest,年龄=21”的学生对象的信息显示格式如下:
No:20190327,Name:doublebest,Age:21
裁判测试程序样例:
#include<iostream>
using namespace std;

请在这里填写答案

int main(){
    char s1[10]="20190327";
    char s2[20]="doublebest";
    Student stu1(s1,s2);
    stu1.setAge(21);
    stu1.show();
    Student stu2=stu1;
    cin.getline(s1,10,'\n');
    cin.getline(s2,20,'\n');
    stu2.setNo(s1);
    stu2.setName(s2);
    stu2.show();
    return 0;
}
输入样例:
20190327
doublebest
输出样例:
Constructor run
NumTotal:1
No:20190327,Name:doublebest,Age:21
CopyConstructor run
NumTotal:2
No:20190327,Name:doublebest,Age:21
Destructor run
NumTotal:1
Destructor run
NumTotal:0
代码长度限制
16 KB
时间限制
400 ms
内存限制
64 MB
问题描述2
复制代码
复制代码
#include<string.h>
class Student{
    public:
    Student(char *,char *,int );
    Student(Student &);
    ~Student();
    void  setNo(char *);
    char*  getNo();
    void  setName(char* );
    char*  getName();
    void  setAge(int);
    int getAge();
    void  show();

    private:
     static int sum;
    char no[20];
    char name[30];
    int age ;
};
int Student::sum = 0;
Student::Student(char *a,char *b,int c=0){
    sum++;
    strcpy(name,b);
    strcpy(no,a);
    cout <<"Constructor run"<<endl<<"NumTotal:"<<sum<<endl;
}
Student::~Student (){
    sum--;
    cout<<"Destructor run"<<endl<<"NumTotal:"<<sum<<endl;
}
Student ::Student(Student &p){
    sum++;
    strcpy(name,p.name);
    strcpy(no,p.no);
    age=p.age;
    cout<<"CopyConstructor run"<<endl<<"NumTotal:"<<sum<<endl;
}
void Student:: setNo(char *str){
     strcpy(no,str);
}
char* Student::getName(){
    return name;
}
void Student:: setName(char *str){
   strcpy(name,str);
}
char* Student:: getNo(){
    return no;
}
void Student:: setAge(int nl){
    age=nl;
}
int Student::getAge(){
    return age;
}
void Student:: show(){
    cout<<"No:"<< no <<",Name:"<< name <<",Age:"<<age<<endl;
}
源代码2
复制代码

三、定义日期类Date

复制代码
定义一个日期类Date,main()函数完成对其的测试。

Date类结构说明:
Date类的数据成员包括:
①私有数据成员:年year(int型),月month(int型),日day(int型)。
Date类成员函数包括:
①定义有参构造函数Date(int ,int ,int )和拷贝构造函数Date(Date &),其中有参构造函数参数默认值为1,输出信息“Constructor run”,拷贝构造函数输出信息“CopyConstructor run”
②定义析构函数,析构函数输出信息“Destructor run”
③公有函数成员:void  setYear(int )和int  getYear()分别设置和返回year
④公有函数成员:void  setMonth(int )和int  getMonth()分别设置和返回month
⑤公有函数成员:void  setDay(int )和int  getday()分别设置和返回day
⑥公有函数成员:void  tomorrow( )和void  yesterday( )实现日期加/减一天操作
⑦公有函数成员:void  printMonthCalendar( )打印当月日历,格式如下(每列宽度为3位,右对齐)(以2016年3月为例)
SunMonTueWedThuFriSat
        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 31
⑧公有函数成员:void  chineseFormat( )和void  americaformat( )分别显示中式日期(例:2014年4月21日)和美式日期(例:Api 21, 2014),其中美式格式中1~12月分别用Jan、Feb、Mar、Api、May、Jun、Jul、Aug、Sep、Oct、Nov、Dec表示
⑨私有函数成员:int  isLeapYear( )确定当前年份是否为闰年,闰年返回1,否则返回0;
⑩公有函数成员:int  weekDay( )返回该天是星期几,0~6分别表示Sun、Mon、Tue、Wed、Thu、Fri、Sat
注:在以上成员函数执行过程中,若输入的日期参数值超界,则按照最接近输入参数的有效日期值对待,例如:

假定输入的month>12,则按照12对待,若输入的month<1,则按照1对待;

假定当前month为12月,则若输入的day>31,则按照31对待,若输入的day<1,则按照1对待;

提示:cout.width(int)设置输出项的宽度

裁判测试程序样例:
#include<iostream>
using namespace std;
int const monthDay[12]={31,28,31,30,31,30,31,31,30,31,30,31};
char* const weekName[7]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
char* const monthName[12]={"Jan","Feb","Mar","Api","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};

/*请在这里填写答案*/

int main(){
    int year,month,day;
    Date d1;
    Date d2(d1);
    cin>>year>>month>>day;
    d1.setYear(year);
    d1.setMonth(month);
    d1.setDay(day);
    d1.yesterday();
    d1.chineseFormat();
    cin>>year>>month>>day;
    d2.setYear(year);
    d2.setMonth(month);
    d2.setDay(day);
    d2.tomorrow();
    d2.americaformat();
    d2.printMonthCalendar();
    return 0;
}
输入样例:
2019 3 27
2016 2 31
输出样例:
Constructor run
CopyConstructor run
2019年3月26日
Mar 1,2016
SunMonTueWedThuFriSat
        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 31
Destructor run
Destructor run
代码长度限制
16 KB
时间限制
400 ms
内存限制
64 MB
问题描述3
复制代码
复制代码
class Date{
    private:
    int year,month,day;
    int  isLeapYear(int year ){
        if(year%400==0&&year%100!=0||year%4==0)return 1;
        else return 0;
    }
    public:
    Date(int a=1,int b=1,int c=1);
    Date(Date &);
    ~Date();
    void setYear(int );
    int getYear();
    void setMonth(int );
    int getMonth();
    void  setDay(int );
    int  getday();
    void  tomorrow( );
    void  yesterday( );
    void  printMonthCalendar( );
    void  chineseFormat( );
    void  americaformat( );
    int  weekDay( );


};
Date::Date(int a,int b,int c)
{
    cout<<"Constructor run"<<endl;
}
Date::Date(Date &obj)
{
    year=obj.year;
    month=obj.month;
    day=obj.day;
    cout<<"CopyConstructor run"<<endl;
}
Date::~Date(void)
{
    cout<<"Destructor run"<<endl;
}
void Date::setYear(int a)
{
    year=a;
}
int Date::getYear(void){
    return year;
}
void Date::setMonth(int a)
{
    month=a;
    if(a>12)month=12;
    if(a<1)month=1;
}
int Date::getMonth(void)
{
    return month;
}
void Date::setDay(int a){
    day=a;
    if(day<1)day=1;
    if(isLeapYear(year)&&month==2){
        if(day-1>monthDay[month-1])day=29;
    }
    else if(day>monthDay[month-1])day=monthDay[month-1];
}
int  Date::getday(void)
{
    return day;
}
void Date:: tomorrow( ){
    day++;//2016 12 31
    if(isLeapYear(year)&&month==2){
        //printf("%d",day);
        if(day>29){

        day-=29;
            month+=1;
        }
    }

    else if(month==12&&day==32){
        //printf("%d",day);
        day-=31;
        month=1;
        year++;
    }
        else if(day>monthDay[month-1]) {
            //printf("%d",day);
        day-=monthDay[month-1];
            month+=1;
    }
}
void Date:: yesterday( ){
    day--;
    if(day<1){
        if(isLeapYear(year)&&month==3)day++;
        day+=monthDay[month-2];
        month--;
        if(month<1){
            year--;
            month=12;
            day=31;
        }
    }
}
void Date:: printMonthCalendar( ){
    for(int i=0;i<7;i++)cout<<weekName[i];
    cout<<endl;
    int k=0;
    int sum=0;
    if(year>=2016){
        for(int i=2016;i<year;i++){
            if(isLeapYear(i))sum+=366;
            else sum+=365;
        }
        for(int i=0;i<month-1;i++){
            if(i==1){
                if(isLeapYear(year))sum+=29;
                else sum+=28;
            }
            else sum+=monthDay[i];
            //printf("%d",sum);
        }
    }//sum 2016 1.到那一天的时间
    if(year<2016){
        for(int i=year+1;i<2016;i++){
            if(isLeapYear(i))sum+=366;
            else sum+=365;
            //printf("%d-",sum);
        }
        for(int j=month;j<=12;j++){
            if(j==1){
                if(isLeapYear(year))sum+=29;
                else sum+=28;
            }
            else sum+=monthDay[j-1];
           // printf("%d-%d\n",sum,j);
        }

    }//那一月到2016.1的时间
    //1 1
    //306%7==  ...5//60%7==....4
   // printf("%d\n",sum);//  3 1  5 59%7=8...3   2 1  31...3
    sum%=7; //tianshu
    //184%7==26...2
    if(year<2016)k=5-sum;//zhout---->sum    5
    if(year>=2016)k=5+sum;//sum<--zhou5
    //printf("%d\n",k);
    if(k<0)k-=6;
    else if(k>=7)k-=7;
    else if(k==0)k=0;
    if(k>0)k=-k;
    while(k<=-7)k+=7;
    for(int i=0;i<5;i++)for(int j=0;j<7;j++){
        if(k>=0)printf("%3d",k+1);
        if(k<0)printf("   ");

        if(j==6)printf("\n");
        if(month==2&&isLeapYear(year)==1){
            if(k+1==29){
                if(j!=6)printf("\n");
                //break;
                i=6;
                return ;
            }
        }
        else if(k+1==monthDay[month-1]&&(month!=2||month==2&&isLeapYear(year)==0)){
            if(j!=6)printf("\n");
            //flag++;
            //break;
            i=6;
            return ;
        }
        k++;
    }
}
void Date:: chineseFormat( ){
    cout<<year<<""<<month<<""<<day<<""<<endl;
}
void Date:: americaformat(){
    cout<<monthName[month-1];
    printf("%2d",day);
    cout<<","<<year<<endl;
}
源代码3
复制代码

每日一mo:每次跑完步,龙山的阴风都刮得耳朵疼。

posted @   寒心小呆  阅读(370)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程
点击右上角即可分享
微信分享提示