C++学习

C++学习

C++学习第一天

1.开发环境介绍

qt creator    qt  c++的开发  IDE---集成开发环境。

vs code 编辑–gcc—gdb
vs2017 主要在windows下使用。
(vs code ) 3天
(qt creator)2天
(vs2017) 3天

2.c++头文件风格

c++中不使用.h的头文件风格。但兼容c语言,可以使用xx.h
c语言的头文件,去掉.h然后在头文件前面加c  ,例如#include<stdio.h> 修改 #include<cstdio>

3.第一个helloworld程序

编写.cpp的代码
#include
using namespace std; //名字空间
int main() {
cout << “hello world”<<endl; //标准输出
return 0;
}
第二步:编译 g++ 文件名.cpp -o 可执行程序名
第三步:运行 ./可执行程序名

4.标准输入输出

cout 标准输出,可以输出基本数据基本数据类型,可以使用多个<<号 (有缓存)
例如: cout << "hello world "<< a << endl;
endl’是换行符,可以放在中间末尾都可以。
setf 函数 (10进制,16进制输出,对齐方式,小数显示方式)
fmtflags setf(fmtflags __fmtfl) //设置前缀,0x 。
fmtflags setf(fmtflags __fmtfl, fmtflags __mask) 设置进制
(提供表格。自行测试)
标准输入: (有缓存)
cin 标准输入
格式: cin >> 变量 >>变量
作用:和scanf相似,把输入的内容存入到变量中。可以多次输入

标准错误 (没有缓存)
cerr 也是属于输出,一般情况下在错误的时候使用cerr。
cerr << “error”<<endl;

标准日志 (有缓存)
clog << “log” <<endl;

5.名字空间(命名空间)

为什么需要?
c++中为了避免名字冲突(名字污染)
模块化的编程。
定义名字空间:
namespace 名字空间名
{
//定义变量
//定义常量
//定义结构体
// 定义函数
//定义枚举联合
//定义名字空间

}

调用方式:

第一种:全部引用。直接使用using namespace 名字空间名
接下来所有在main中每找到的内容,都在以上名字空间中找。
接下来的变量和函数直接按照普通变量和函数使用。

第二种:部分引用方式, using 名字空间::函数/变量
如果当前函数(变量)没找到,则从名字空间中::函数中找

第三种: 使用域调用: 在每个变量或者函数前面加上名字空间名
例如: std::cout std::cin std::endl

名字空间嵌套
格式: namespace 名字空间1:
{
namespace 名字空间2
{
int age;
}
}
嵌套名字空间使用:
全部引用: using namespace 名字空间1
部分引用: using namespace 名字空间1::名字空间2
域调用: 名字空间1::名字空间2::变量 = 10

//在华清名字空间中 有danny名字空间,请问danny中的变量怎么使用。
华清名字空间中有age name 有fun函数,danny名字空间中有age 和show函数

6.内联函数:(笔试题–内联函数和非内联的区别)

作用:
在c语言中,如果某个函数频繁调用,就不断的函数入栈和出栈的过程,需要消耗内存空间,并且效率低下,所以在c++中引入了inline修饰,加上后,直接把函数内容复制在hand调用的地方,减少开销,效率更高。

定义内联的要求:
内联函数中不允许有循环,也不可以有switch语句
内联函数不可以超过10行,一般情况下是5行左右。
如果达不到内联的要求,尽管加了inline关键字,也不会按照内联处理
在c++的类中,如果函数没有加inline默认都是内联函数。
定义:直接在函数前面加inline就行。

案例:写一个内联函数,实现比较两个值的较大值。 在main中调用。
比较内联和去掉内联的时间长短。

优势:
减少开销,调高效率

7.函数重载(重点)

定义:在同一个作用域,函数名相同参数列表不同的多个函数。(const函数和非const函数构成重载)

 参数不同: 个数不同
                   类型不同
                  顺序不同
目的: 减少用户的调用复杂性,调用一个函数实现不同的效果
            减少函数名的数量,防止名字污染。
           一般是用来出来类似问题但不同参数的情况。

注意: 再类中const函数和非const函数构成重载。
反编译:objdump -d a.out > danny.txt
int add( int a ,int b) —> _z3addii
void add(int a,float b,double c)–>_z3addifd
在_z3 是返回值类型,add是原本函数名,i表示int ,f表示float,d表示doble
s表示string l表示long,c表示char类型,(改名规则)

匹配规则:

精准匹配: 实参和形参的类型和个数都匹配,不需要做类型转换,(可以做const和非const转换)

提升匹配:实参的类型往上提升,达到满足参数的重载函数类型(传入的bool类型,可以向上转int,char 转int,short转换int )(float 转成double)

使用标准转换匹配(int转换成double、double转换成int),double可以转换成long double int可以转换成unsigned int 这几种叫做标准转换

省略号匹配(如果所有匹配不上则用省略号匹配)省略号也匹配不上则报错。

8.缺省参数

作用:当定义一个函数时候,如果参数列中某一个或者多个参数给默认值,调用时候可以给实际参数留空,这种参数叫缺省参数。
格式: 返回值 函数名(类型 参数1=值,类型 参数2 = 值)
调用: 调用时,有默认值的可以不传入参数。
注意事项: 在调用缺省参数的函数时,实参的传值是从左向右传入,中间不可以有空参数。
在定义缺省参数时,形参的缺省从右向左,中间不可以有空缺省。
实参个数必须大于或者等于 无默认缺省参数的个数,不可以多余实际形参个数
缺省参数只有在声明的时候使用,在定义时不需要加缺省。
缺省参数会导致函数重载的二义性
int add(int a ,int b ,int c = 1,int d= 1, int e=1);
int add(int a , int b, int c = 1)

9. extern “C”

作用: 为了能够实现在c++中调用c语言的代码。所以使用extern “C”
因为c语言不会修改函数名,c++会修改,所以在c++ 和c 混合编程的时候,希望么某些函数按照c语言的风格编译,所以会出现。
使用: 在函数定义前加extern"C"
原理:在c++中,默认定义了__cplusplus宏,定义之后按照c++的风格编译。

案例:
extern “C” void add(int a,int b) 按照c语言的规则编译,不会修改名字
{
cout << a + b << endl;
}
void add(int a) //按照c++的编译规则,修改成_z3addi的名字
{
cout << a << endl;
}
注意: extern 的c是大写C
如果几个函数加了extern “C” 则函数名不可以相同,c没有函数重载。
如果需要按照c++的规则 则可以使用 extern “C++” void add(int a);声明函数
如果多个函数都需要使用extern “C” 可以在extern "C"后加大括号,把所有需要声明的函数都定义在extern"C"的大括号中。

10.引用:

引用:是给某一个变量取别名。别名和变量同生死。
格式: 类型& 别名= 变量名;
注意: 别名一定是在定义的时候需要给他赋值。不可以单独定义引用。必须要初始化
int* p = &x;
int& p = x
一般情况下,可以作为函数的参数和函数的返回值。
函数传参的方式: 值传递,地址传递,引用传递

注意: 不可以返回局部变量的引用,可以返回局部静态变量引用。
优势: 使用引用当做函数参数,不会产生临时变量,节省内存空间。
补充: static 是静态变量,如果定义局部静态变量
修改局部变量的生命周期,从定义开始到程序结束,只会初始化一次。
不会修改作用域。

面试题:引用和指针的区别

第一条:引用必须初始化,指针不一定。
第二条 引用不占用内存空间,指针占用
第三条 引用一旦初始化就不可以修改,指针可以修改
第四条 有指针的引用 没有引用的指针。
第五条 不存在空引用,但存在空指针。

常引用
定义: 在引用前加const ,表示常引用。
注意: 常引用不可以通过别名修改内存空间的值。但可以通过原名修改内存空间
一般用于函数参数,防止函数中修改引用的值。
例如: const int& pa = a; pa++ 是错误的,a++是可以的。

11. new (重点)

作 用: 和malloc相似,在堆区申请内存空间
格 式: 指针变量 = new 类型 开辟一个类型空间,不会初始化
指针变量 = new 类型(初始值) 开辟一个内存空间,会初始化成初始值
指针变量 = new 类型[数组大小] 开辟数组大小个内存空间,不会初始化。
memset bzero
释放: delete 指针名 释放内存空间(一个)
delete[] 指针名 释放多个内存空间
能力守恒: malloc------free new--------delete

malloc 和new的区别(面试)

 new 申请对应的释放是delete  malloc申请对应的释放是free
malloc申请数组直接在括号中传入数组大小,new直接用中括号
  new申请失败会返回异常,malloc失败返回NULL
 new 和malloc都是在堆中申请的内存空间
 new不需要强制类型转换,申请的是对象,malloc需要强制类型转换

强制类型转换

c语言中强制类型转换 (type)变量
c++ 的转换方式
取消const属性:
const_cast<类型>(变量);

用于多态的转换(对象之间的转换)
dynamic_cast<类型>(变量)

c++强制类型转换
static_cast<类型>(变量名)

按照二进制方式拷贝转换(类型只可以是指针)
reinterpret_cast<类型>(变量名)

C++学习第二天

一、面向对象基本概念:

1.定义学生结构体:有名字 有年龄 有性别,有show函数,打印学生信息

2.结构体: 结构体可以理解成类,定义的结构体变量可以理解成对象。

结构体在c和c++的区别

     c++中定义结构体对象的时候可以省略struct关键字
     c++中定义结构体可以有函数,并且可以有函数的实现。
     c++中结构体成员都是共有的,在任何地方都可以使用。

3.类的定义

  格式: class  类名
             {
              访问修饰符:
                    定义变量;
               访问修饰符: 
                     定义函数;
             };

4.访问修饰符:

    public: 共有属性,在类内,在类外,在子类中都可以使用。
    private:私有属性,只可以在类内使用,不可以在类外和子类中使用
    protected:保护属性,在类内和子类中可以使用,不可以在类外使用。

5.get函数和set函数

  对于所有成员和保护成员,提供外部两个函数,一个是get得到私有成员,一个是set设置私有成员。
  类型  get变量名( ){ return 变量;}
  void   set变量名(类型 设置的值) {  成员变量 = 需要设置的值;}
   练习:定义一个盒子 类,有私有的长度,有共有的高度,有保护的宽度,还要一个共有的计算体积的函数,在main函数中定义对象,然后调用计算面积的函数打印出面积。

6.类内声明类外实现

实现格式: 返回值 类名::函数名(参数){  }

注意:在类外实现可以使用类中的私有成员变量,因为函数的申明在类内,属于类的函数

7.对象定义方式

 //第一种定义对象方式
person abc;
// 第二种定义对象方式
person * pson = new person; 
 注意:如果是第一种方式,直接使用点访问成员变量,如果是第二种方式,直接用箭头方式->方式访问。
函数传参的方式:值传递。指针传递。引用传递。

8.构造函数

   什么是构造函数:
     在定义对象时,需要先确定某一些成员变量的初始化,所以需要调用一个特殊的函数来构建对象,这个函数叫做构造函数。
  格式: 类名(参数) { 函数体}
  特点:没有返回值,不可以写void。
            函数名是类名,不可以随便定义构造函数的函数名。
            在构建对象时调用,系统自动调用。   
            如果没有手动写构造函数,编译器会自动添加一个无参的构造函数,默认调用。
           一旦自己写了构造,编译器就不会添加无参的构造函数。
   
注意:如果定义了有参构造,编译器添加的无参自动取消,定义对象时就需要添加参数
         如果一个空类,什么都不写,默认是1个字节,表示类存在。

 练习:写一个动物类,有名字,有性别,写两个构造函数,一个是无参,一个有两个参数,请在main函数中定义对象,打印出成员变量的值。

9.析构函数

   销毁对象时会自动调用析构函数,系统自动调用,不可以手动调用。
   特点:不可以自己调用,没有参数,没有返回值,函数名是类名前加~
   格式 : ~类名() {   }
   处理:文件关闭,内存释放,释放句柄,关闭描述符,善后工作
  调用析构的时间:
       定义的局部对象,当函数结束则自动调用析构释放对象。
      当new的对象delete时,会调用析构。
      当程序结束会调用析构函数释放对象。
      当创建临时对象时,当前行结束则调用析构释放对象。(匿名对象)

10.构造函数的初始化列表

      c++在执行构造之前,先会调用构造函数的初始化列表,主要是对成员函数赋值。
    格式:类名(参数):成员变量(值),成员变量2(值)
              {
              }
    初始化列表的执行顺序:
     class person
    {
             int age = 12;   //直接赋值是最先执行
     public:
           person(int a,int s):age(a),sex(s)    //初始化列表第二执行
           {  
             age = 123;  // 函数体中,最后执行。
            }
    };
   注意:初始化列表中的顺序和初始化顺序无关,只和成员变量的定义顺序有关

11.拷贝构造

   拷贝构造也属于构造,没有返回值。用另外一个对象构建出一个新的对象。
   格式: 类名(const 类名& 其他对象){ }
   什么时候调用拷贝构造:
       当函数形参是一个普通对象,则会调用拷贝构造来创建形参对象
       person wang(danny);  把另外一个对象当做构造函数的参数时
       person wang = danny;  当用另外一个对象赋值给其他需要创建的对象时。
练习:有一个人类,有姓名(int age),有年龄(char* name),请写构造函数,析构函数,拷贝构造函数,然后在全局fun函数中定义普通的danny对象,拷贝构造static的王对象,然后在main函数中把wang的名字打印出来。
浅拷贝: 只拷贝指针(只给指针赋值),两个对象指向同一块内存空间(double free)
              如果没有写拷贝构造,编译器会自动添加默认的浅拷贝构造函数。(如果类中没有指针,可以使用默认)
深拷贝: 新对象重新申请了内存空间,拷贝了other对象的内容,不是指针的赋值。
                如果自己写了一个拷贝构造函数,默认拷贝构造就会消失。

12.this指针

  定义: 在每个对象中都有一个特殊的指针this,他表示对象本身,表示自己的意思,是代名词
  使用:没办法区分成员变量还是参数的时候使用,this->age = age;
  在每一个成员函数中都默认传入了this指针。
  注意: 友元函数中没有this指针。
            static的静态函数中没有this指针     

13.友元函数

  友元函数:可以访问类中的所有成员变量和成员方法,(共有私有保护都行)  
                 友元函数中只可以通过对象的方式访问到成员变量,没有this指针。
  格式:friend   返回值 函数名(参数){ }
  注意: 友元函数只有在声明的时候需要加friend 实现时不需要加。也就是实现和定义分开时定义不需要加friend关键字
            友元函数可以重载;
           定义和实现分开时,在定义部分不需要加类名限定符。

C++学习第三天

一、友元类

 一个类是另外一个类的友元
 A类是B类的友元,是A可以访问B的所有内容。
 格式:
    class 类名1{
     public:
           friend class 类名2;  
    }
   类2是类1的友元类,类2可以访问类1中的所有成员变量和函数
  class 类名2
  {
         void  show(类1& 别名){};
   }
  练习:写一个点类,点类中有两个私有成员变量 int x和y,有构造和拷贝构造和析构,另外一个类是 表格类,其中点是表格类的友元类,表格类中有 表格的int 行和列,还有show函数。请在点类中,调用setxx方法吗,把表格类中的行数和列数设置成点中的x行和y列。

二、类的静态成员

 2.1格式: 在类中  static 类型 变量
 2.2特点:静态成员变量只属类,不属于对象。
          所有对象共有一份静态成员变量。
         如果一个对象修改的静态成员变量,其他的成员也被修改了。
         静态变量的初始化在类外。
 2.3注意: 静态成员变量需要在类外初始化。 int person::m_AllNum = 0;
            静态变量在定义的时候需要加static,在类外赋值的时候不需要加static
2.4访问方式: 第一种,直接通过对象点的方式 wang.m_AllNum = 80;
                  第二种:通过类名限定符的方式访问 person::m_AllNum = 10;
2.5什么时候使用:同一个类的不同对象想共享某一个数据的时候访问。

三、类的静态函数

 3.1定义:static 返回值  函数名(参数列表) {}
 3.2特点:
        静态成员函数属于类,他没有this指针,不可以访问类中的成员变量
        静态成员函数可以访问静态成员变量。
        静态成员函数和非静态成员函数的本质区别是,静态成员函数没有this指针。
3.3访问方式: 对象点的方式   danny.show(10);
                 类限定符的方式: person::show(10);
3.4什么时候使用:  静态成员函数来判断某些对象是否已经创建。

自学: 静态成员函数中怎么访问普通成员变量呢???       

四、类的const成员变量

  定义: const 类型  变量;
  特点:  const成员变量初始化后不能修改。
 初始化: 第一种:在定义const成员变量时直接初始化  const int x = 1;
               第二种:在构造函数的初始化列表中进行初始化 person():m_AllNum(1){}

五、类的const成员函数

5.1定义:返回值 函数名(参数)const{ }
5.2特点: const成员函数不可以修改成员变量的值。但可以访问。
          const成员函数不可以调用非const成员函数
          可以访问非const成员变量
         const成员函数和非const成员函数构成重载(尽管函数名相同,参数相同)
       const对象只可以调用const成员函数,不可以调用非const函数
5.3注意:非const对象优先调用非const函数,如果没有非const函数时,就调用const函数
             如果在全局函数的参数中是const的对象,在全局函数内部只可以通过对象访问到const成员函数,非const成员函数没法使用。

六、运算符重载(重点)

 6.1有哪些运算符
      算术运算符: +  -  *  / %  (加号)
      逻辑运算符: ||       &&        !  (逻辑与)
      关系运算符: >   >=   <  < =   ==   !=     (大于 和等于等于)
      赋值运算符:  =   +=  *= /= %=  >>=   <<= |=  &=  (赋值运算符)
      三目运算符: ?:
      二进制运算: | & ~   >>    <<
      自增自减:   ++i  , i++,    --i,i--
      单目:   +(正号)  -(负号)   &(取地址) *(解引用) 
     其他运算符: ->    [  ]   ( )      ,     ::    .     #  new   sizeof    delete
    不可以重载的:三目运算符, .(点)   sizeof   .*      #     ->*
 6.2 关键字: operator
 6.3 格式: 返回值 operator运算符(参数){函数内容}
                 可以修改的部分 返回值 ,参数可以修改, 函数内容可以修改
 6.4三种实现方式:
        第一种:成员函数实现方式  
                     双目: 类名& operator-(类名& other)
                     单目    类名& operator-()
         第二种: 友元函数实现方式
                    双目:friend box operator*(const box& other,const box& my);
                    单目:friend box operator*(const box& other);
          第三种: 全局函数的实现方式
                   单目:box operator*(const box& other)
                   双目:box operator*(const box& other,const box& my)

6.5 注意:运算符重载后,重载后的运算符可以按照原来的风格调用。
               不可以重载新的运算符(比如$),也不可以修改原来运算符的结合方式。a++b
               空类中,默认添加无参构造,析构,this,浅拷贝 。赋值运算符重载。
  
6.6 特殊运算符的重载
      输出流运算符重载(不可以成员函数方式) 
        ostream& operator << (ostream& os, const box& other)
      输入流运算符的重载(不可以成员函数)
        istream& operator>>(istream& is,box& mybox)
       单目运算符重载(全局友元成员都行)
           box operator-()  { }             //负号号运算符成员函数实现方式
          box operator-(const box& other)  //减号运算符成员函数实现方式
        自增自减运算符
         前加加成员函数实现方式;
         后加加成员函数实现方式
         后减减全局函数实现方式;

  赋值运算符(只可以成员函数实现)

C++学习第四天

一、继承

 1.1为什么用继承:达到重用代码的功能,提高开发效率
 1.2基本概率:  父类(基类)--- 子类(派生类)
                   例如: 动物类 (父类)   鸟类(子类)
1.3格式:class 类名:限定符  父类  {       };
 1.4注意:限定符有 public  private   protected

1.5限定符的区别:

       public继承后,父类的public子类中变成 public 
                           父类的private成员变量子类不可以使用
                           父类中的protected子类中也是保护属性
                         (除父类私有属性外其他属性在子类中不改变)
       私有继承:private (私有继承变私有)
                       父类中的public属性子类中private
                       父类中的private属性子类中不可以访问
                       父类中的protected属性子类中private
      保护继承:(保护继承变保护)
                      父类中的public属性子类中protected
                      父类中的private属性子类中不可以访问
                     父类中的protected属性子类中protected

1.6子类不可以继承的内容:

              父类中的构造,析构,拷贝构造
              父类中的友元函数(友元不属于父类)
              父类中的静态成员变量
              父类的重载的运算符

1.7构造和析构的调用顺序

             先构造父类,在构造子类,先析构子类,再析构父类。
             子类的构造函数中调用了父类的默认无参构造;
    注意: 如果父类中没有无参构造,子类的构造函数中必须主动调用父类的有参构造函数,在初始化列表中调用,brid::brid(int a,int b,int c):bbb(b),animal(a,b,c)

二、多继承:

 2.1 定义:一个子类有多个父类。子类中有多个父类的成员函数和成员变量
 2.2 出现问题: 可以能出现菱形继承或者环形继承
 2.3 格式:class 子类:继承属性 父类1,继承属性 父类2{  }
  2.4构造和析构调用顺序
      根据继承的先后顺序进行构造,先构造第一个父类,再构造第二个父类,再构造子类;例如: class A:public b,public c{},则先构造b类再构造c类再构造a类。

三、内部类和局部类:(很少)

   3.1内部类定义: 定义在其他类中的类叫做内部类;
   3.2格式:  class A {  public:    int x;
                             private:  int y;
                                  void show(){是否可以使用内部类成员变量}
                             public:
                                  class  B  {  
                                       private  int z; 
                                       public: int q;   
                                       void show(){使用外部和内部}     
                                    } ;
                       };
    3.3说明:
         3.3.1 外部类不可以使用内部类的成员变量;
                 内部类可以通过对象的方式访问外部类的成员变量和方法;
                 内部类可以直接访问外部类的static成员变量
                 内部类可以在类内声明 在类外实现;
         3.3.2 内部类可以在内类声明 类外定义 也可以直接在类内定义
    
class A{
	public:
	 static int y;
	 A(int a)
	  {
    			cout<< "我是A类的构造"<<endl;
		}
	public:
		class B    { };
	  class C;  //在类内声明C类
};
int A::y =1;
class A::C{
	 C(int a)
		{
   		 cout << "我是C的构造"<<endl;
		}
		 void show();
 };
void A::C::show()  {
		cout << "我是c的show函数"<<endl;
}
 3.4局部类(用得少)
 定义:定义在局部的类,一般在函数中,只可以在函数中使用。
 注意:只可以在函数内部定义对象,不可以在函数外定义。
          函数中不可以直接使用类中的成员变量和成员方法,只可以通过对象的方式
          局部类中也不可以使用类外函数中的普通变量,只可以使用static的成员变

3.5 匿名对象(lambda表达式)

       匿名对象--也叫临时对象,在当前行定义 在当前行析构
       特点:  没有变量名,没有被指针指向的对象。
       格式:类名(参数)
 3.6 构造函数中调用构造
        在初始化列表中,可以调用其他的构造函数。
        目的: 为了介绍构造函数中的代码量,防止重复代码。

四、多态(超级非常特别重要)

4.1定义: 多态是面向对象的一个非常重要的特征
            父类的指针,指向子类的对象,通过父类指针可以出现不同的效果。
4.2要求:有父子类继承关系 
              父类的指针指向子类对象 (或者父类的引用 引用了子类对象)
              子类重写了父类的成员函数(??)
4.3虚函数:virtual 返回值 函数名(参数){ }
4.4重写(覆盖): 如果父类定义了虚函数,子类重新定义了父类函数,此情况叫做子类重写了父类的方法或者叫做覆盖了父类方法。
 4.5注意:如果父类函数是virtual的,子类加没加virtual都是虚函数
                如果父类是虚函数,子类不是虚函数,父类指针指向子类对象调用的是子类
                如果父类是虚函数,子类也是虚函数,父类指针指向子类对象调用的是子类
                如果父类不是虚函数,子类时虚函数,父类指针指向子类对象调用的是父类
                如果父类不是虚函数,子类也不是虚函数,父类指针指向子类对象调用的是父类

4.6必问面试题(重载,重写,覆盖,隐藏的区别是什么?)

   重载:在同一个作用域类,函数名相同,参数列表不同,加virtual或者不加都没关系。
             不可以在父类类之间,并且参数列表不相同,不是相同的函数
  
   重写(覆盖):子类写了一个和父类一模一样的虚函数
             一定是在父子类关系中
             一定有虚函数,子类可以省略virtual。
   覆盖: 覆盖就叫重写,是应为不同的人叫法不一样而已。

   隐藏:子类写了一个父类相同函数名的函数
              首先有父子类关系
              子类有父类相同的函数名,参数相同或者不相同都没关系
               如果函数名相同参数不相同,不管父类是否是虚函数,都属于隐藏

4.7 纯虚函数

      原因:在多态中,父类的指针调用的都是子类对象,不会调用父类的虚函数,所以父类的虚函数有函数体和没函数体都是一样的效果。但是函数声明了就需要定义。
       纯虚函数: virtual 返回值 函数名(参数)=0;
       注意:如果父类定义了一个或者多个纯虚函数,则父类是抽象类。抽象类不可以定义对象
                  父类有纯虚函数,则子类必须重写,如果子类不重写,则子类也是抽象类。  
                  如果父类所有函数都是纯虚函数,则叫做接口(类)。

4.8 虚析构

    析构函数最好定义成虚函数。也可以定义成纯虚函数,但定义纯虚函数后,父类就是抽象类。
    在多态时,如果子类中的成员变量开辟了堆空间,呢么父类指在释放的时候,没法调用子类的析构

4.9 虚继承(不重要–用来解决菱形继承而存在—自学)

  格式:class a:virtual public b{ };
  17373132566(姓名)
4.10多态
    父类的指针指向子类的对象,通过父类指针调用实现不同的效果。
 4.7虚函数表(虚表)

补充:qt ctreator使用的简单

 1。安装程序(直接下一步)
 2.点击new protject
  3.选择non-qt-project
   4.选择plain c++ application 点击choose
   5.在名称中输入项目名称(不用中文名)
   6.接下来一直下一步下一步
   7.编写代码 ---点击左下角绿色图标运行
  基本功能:左侧栏编辑--用来编写代码,显示文件
                  左侧栏项目--用来设置项目的,在pro文件中添加内容
                  第二列项目下拉框--可以选择类视图--文件视图   
                  字体调大:ctrl+鼠标滚轮

C++学习第五天

一、字符串string

 string本质:c++中的字符串,string本质是一个类,内部维护了一个char*
 为什么使用:string重载了超级多的运算符,有超级多的成员函数使用,用起来方便。
  可以在MSDN中查看帮助:https://docs.microsoft.com/zh-cn/cpp/standard-library/string?view=msvc-170
 构造:
   第一个: string()  无参构造,创建一个空的字符串 “”
   第二个:string(const char*) ; 输入一个字符串,构建一个对象
   第三个:string(const string& str); 拷贝构造,用一个string构建另外一个string
    第四个:string(int n,char c);用n个c来初始化string 
 字符串的赋值(给字符串赋值修改原来字符串内容)
        string& operator=(char *s)   //1
        string& operator=(string &s)
       string& operator=(char c)
       string& assign(char* s); 
        string& assign(string* s)  //2
        string& assign(char* s,int n);  //把s字符串的n个字符赋值给原本字符串
 字符串的拼接(在字符串sting后追加新的字符串)
       string& operator+=(char *s)  
        string& operator+=(string &s)//1
       string& operator+=(char c)
        string& append(char* s);
        string& append(const string&* s)  
        string& append(char* s,int n);//追加字符串s中的n个字符到原来字符串后
       string& append(const string& str,int pos,int n)//把字符串的str中pos+1开始的n个字符追加到原来字符串后

字符串的查找(替换)
    int find(const string& str,int pos=0)   在pos开始的位置 查找str子字符串,返回是位置
    int find(const char *str,int pos=0) 
    int find(const char *str,int pos,int n )  从pos位置查找字符串str的前n个字符第一次出现的位置 
    int find(char c,int pos = 0);    从pos开始查找字符c在字符串的首位置
    int rfind(const string& str,int pos=0)  ;从后往前查找,返回找到的位置

字符串的比较
int compare(string& s); //把原字符串和s字符串比较,请问返回值是什么?
int compare(char* s) // 把原字符串和s字符串比较,
如果第一个字符串和第二个字符串前部分相同,但第一个要长,返回长的字符个数
如果第一个祝福词和第二个字符串一样前部分一样,但第二个长,返回少了几个字符
如果第一个和第二个完成不一样,返回-1;
如果第一个

二、文件操作

c中 标准io:fopen,fread fwrite fclose fseek rewind ftell fgets fputs fprintf fscanf fputc fgetc
c中 文件io:open read write lseek close fcntl

流操作方式
以前学的流操作: cout cin cerr clog
文件的流操作: fstream 类 (对文件读写) ofstrem(out–写文件) ifstream (in—读文件)

两种类型文件:
文本文件:
写文件: 1.创建ofstrem对象 oftream os;
2.打开文件,用ofstream的成员函数open打开
void open(const std::string& __s, ios_base::openmode __mode = ios_base::out | ios_base::trunc);
作用:打开文件;
参数:s–需要打开的文件名
__mode 需要打开的方式
ios::out 输出方式打开文件(写文件)
ios::in 输入的方式打开(读文件)
ios::app 追加的方式打开文件(写文件)
ios::binary 二进制的方式打开文件
ios_base::trunc 文件不存在则创建,存在则清空文件
3.写文件内容
os << “内容”<<endl;
4.关闭文件。os.close

  读文件: 1.创建ifstrem对象       iftream os;
                 2.打开文件,用ifstream的成员函数open打开
                    void   open(const std::string& __s,  ios_base::openmode __mode = ios_base::out | ios_base::trunc);
                    作用:打开文件;
                    参数:s--需要打开的文件名
                           __mode  需要打开的方式
                           ios::out    输出方式打开文件(写文件)
                           ios::in       输入的方式打开(读文件)
                           ios::app    追加的方式打开文件(写文件)
                           ios::binary 二进制的方式打开文件
                           ios_base::trunc  文件不存在则创建,存在则清空文件
                 3.读文件内容
                        第一种:os >> buf
           第二种: os.getline()
                      char     getline(ifstream __in, string& __str,   char __delim);
                       第三种: os.get() 

                 4.关闭文件。os.close
          做文件的拷贝功能,写一个程序,把huaqing.txt的内容拷贝到huaqing-bak.txt中去。
  
二进制文件:
    打开方式中需要加入:ios:: binary   二进制打开方式
    写文件:ostream&  write(char* str,size_t n);
                 作用: 写二进制文件
                 参数: str 需要写的内容
                             n是需要写的长度
                 返回值:输出流的对象 
   读文件: istream& read(char* str,size_t n)
              作用: 读二进制文件
                 参数: str 需要读的内容
                             n是需要读的长度
                 返回值:输入流的对象 
   文件末尾的判断
                    is.eof()  判断是否到了读文件末尾,如果到了末尾返回真。
                    os.bad() 如果在读写过程中出现错误,返回true (自己查下)
                    os.fail()  读写过程中出现错误则返回true

文件偏移位置:
对于iftream (get)和ofstream (put)都有输入和输出的偏移量,读取的位置和写入的位置
可以通过一些函数获取到或者可以设置偏移位置
seekg(偏移量,起始位置) — 设置输入流的偏移位置
seekp(偏移量,起始位置) -----设置输出流的偏移位置
tellg() ----获取输入流的偏移位置
tellp () ----获取输出流的偏移位置
偏移起始位置如下:ios::beg 从流的开始位置计算
ios::cur 从流的当前位置开始计算
ios::end 从文件流的末尾开始计算

异常:

  什么是异常:是错误处理机制,当遇到问题后,不可以直接exit或者return时,需要给上一层处理。
  流程:
             try(检查)----->throw(抛出)----->catch(捕获)
 throw:当出现问题时,程序需要抛出一个异常,通过throw+类型 抛出;
             比如打开文件失败后,throw一个异常,告诉别人我出问题了,需要别人帮忙解决。
 try:   try中的代码是用来检测是否有异常,后面一般跟着catch
 catch: 当出现问题后,try检测到问题后,catch来处理问题,防止出错。
 格式:
        try{ 各种代码;}catch(类型 变量){处理出现的问题}

小练习:用c++中文件流的fstream做文件的拷贝(danny1–>danny2),如果流出问题了,需要异常处理,打开文件错误也需要异常处理,并且是不同的异常类。

C++学习第六天

一、

1.函数的异常声明:

   在函数申明的时候,确定抛出异常的类型,增加代码的可读性,并且确定好丢的异常后,更好的捕获异常
   格式 : 返回值  函数名(参数列表) throw(类型1,类型2,类型3 ){ }
  注意:如果不希望函数丢异常,则throw后直接加小括号

2.自定义异常类

 定义一个类,可以用于throw中。
 (补充笔记)

3.标准异常

 从标准的exception类继承的子类,处理异常。
  exception中定义好的异常
  std::bad_alloc     通过new抛出,
  std::domain_error  使用了无效的数字域
  std::length_error  当创建了一个太长的string会抛异常。
  std::overflow_error 数据类型上溢出
  std::underflow_error 数据类型下溢出
 定义标注异常
 class 异常类:public exception{}
 注意:一般情况下需要重写what方法。
  异常嵌套:在异常中 继续try和catch是可以的

ry
{
errorEx err(1);
err.show();
}
catch(errorEx& e)
{
cout << “catch”<<endl;
cout << e.what()<<endl;
}
catch(myexception& er)
{
try{
cout << er.what()<<endl;
}
catch(…) { }

二、模板:

1.泛型编程:

用一个标志来表示类型,不是实际的类型。独立于任何特定类型的编程,c++的一个部分。
PPT模板---他是一个框架,需要填写内容--方便开发,提高了复用度。

2.函数模板:

格式:template<class T1,typename T2>
返回值 函数名(参数){ }
3.解释:
template ----- 声明创建模板
typename ----- (也可以用class代替)表明后的类型是一个通用类型,或许叫做虚拟类型
T1-------------- 通用类型或者虚拟类型,(理解成类型)可以在函数中直接当做普通类型使用。

4. 函数模板的调用:

 3. 第一种:  函数名 <实际类型1,实际类型2>(参数列表);
显式类型推导 fun(1,2);
  第二种: 函数名(参数);
隐形类型推导
5.注意:显示类型推导 参数和推导的类型必须一致。
  如果有普通函数和模板函数,在调用时可以用显示调用,省略类型的方式  swap<>(1,2)

6.普通函数和函数模板的区别:

函数只可以有一种数据类型相匹配。模板有多种类型
隐式推导优先使用普通函数,只有普通函数不匹配才使用函数模板
函数模板只有在调用时,才会构建函数,而普通函数是在编译时。
普通函数调用时候可以发生自动类型转换,而函数模板不行。  

7.函数模板重载

和普通函数的重载相似。也是同一个作用域类函数名相同参数列表不同。
 以下是参数顺序不同的重载
 template <class T1,class T2>void swap1(T2 a,T1 b)
  template <class T1,class T2>void swap1(T1 a,T2 b)
 注意,在函数参数顺序不同的重载中,实例化的时候不可以是相同类型
 例如  swap1<int ,int >(1,2)则没法匹配哪个函数。
 以下是参数个数不同的重载
     template <class T1,class T2>   void swap1(T1 a,T2 b)
     template <typename T2>  void swap1(T2 t)
     传入不同的参数,调用不同的版本
  练习:写比较 大小函数模板,传入2 3 4数值都可以比较都可以返回最大值

8.函数模板特化

定义:为了解决函数模板的局限性,在定义函数模板时候,直接确定好T的类型。也就是特定的类型模板。
格式: template 返回值 函数名(类名& 对象){ }
匹配:如果传入的是自定义类型,并且和特化版本匹配,会优先使用特化版本
注意:如果特化后,类型确定才可以使用自定义类型中的成员变量和方法。

9.多文件实现函数模板

 在定义函数或者类的时候,会.h和cpp文件,定义和申明分开。
 出现连接错误的原因:函数模板是在调用时确定的版本,而调用时.h中没有函数实现,出现连接错误,找不到函数体。
 如果分开后,编译会出现连接错误。
 第一种办法:在main中#include <xxx.h>  和 #include <xx.cpp>
 第二种办法:模板的定义和申明都放在.h头文件中。

10. 函数模板的嵌套

 定义:在函数模板中调用另外一个函数模板
 练习:写两个Mymax函数模板重载,三个参数中调用两个参数的版本比较

11.函数模板的非类型参数

 希望传入函数一个特定的确定参数,但不希望放在小括号中。
 可以通过非类型参数实现。在模板<>中加了一个参数,但这个参数不是类型参数,是具体的某一个值。
 格式:template<class T,基本数据类型 变量名>
           返回值 函数名(T& 变量)
  例如:template <class T,int size>
             void showArr(T* arr);
  注意:在上例子中,size是通过模板传入到函数中,可以当做普通变量使用
            非类型参数都是常量,在函数中不允许修改,只可以使用,定义非类型参数变量时候,需要加const。   

例子: 写一个函数模板,打印数组的所有内容。(但不可以在参数中加入数组大小)。

12.类模板

 建立一个通用的类,如果希望成员变量的类型是任意的,用类模板实现。
 类模板不是一个实际的类型,是虚拟的,和ppt模板一样,实例化的时候才会构建类。
 格式:template<typename T>
           class test { T age; };
  实例化: 类名<类型> 变量名(参数);
 类模板和函数模板区别
  函数模板可以使用隐式类型推导,但类模板不可以,必须显示推导
  类模板在定义template时候,可以加上默认参数。template<class T1,class T2=string>
  在模板中的默认参数类型中,如果所有模板参数都是缺省,但在类进行实例化的时候,尖括号不可以省略template<class T1=int,class T2=string>

class person{}; person<> wang(3,“wang”);

13.成员函数创建时间

作业:做一个函数模板,用选择排序对不同数据类型进行排序(降序的方式)
可以传入多个参数,并且也支持非类型参数的版本。
问题:在类模板中定义函数模板可以吗?

C++学习第七天

一、 类模板对象当做函数的参数

//第一种方式,直接传入模板对象

void fun(person<string, int>& per)

/类模板传入方式

template <class T1,class T2>
void fun3(person<T1,T2>& per)

//整个类模板化

template
void fun4(T& per)

二、类模板继承

普通类继承:class son:public father{}

 模板类继承:普通子类继承模板类;
 格式:class 子类:public 父类<指定类型>
 构建对象:和普通对象的构建一样 类名 对象(参数列表);

类模板继承类模板

格式: template<class T1,class T2>
class son: public person<T1,T2> {}

三、类模板成员函数类外实现

在类模板中,函数的声明和定义都应该在h文件或者在mian的cpp中,在类中声明,在类外定义需要加上模板的类限定符
案例:template <class T1,class T2>
class myson :public person<T1, T2>
{public:int display(T1 x);};
//;类内声明 类外实现(模板类成员函数)
template <class T1,class T2>
int myson<T1, T2>::display(T1 x){}
}
小秘密:一般情况下会把定义模板类的文件后缀修改成.hpp的形式。
练习:实现一个通用的数组类 ( )
第一点:他需要支持基本数据类型和自定义类型。t* parr,int size,int cap) cap是容量,已经存了多少元素。如果存了6个则cap=6
第二点:通用数组中的数据要放入堆内存(new),需要提供
第三点:需要实现构造 析构 有参构造函数
第四点:需要实现==运算符重载,下标运算符[]重载。
第五点:提供两个函数,可以给数组进行为插和尾删除。

四、 类模板的友元函数

1.类外实现友元函数:

       先需要在类内声明函数,声明的时候需要加空模板列表,也就是函数名后<>
       其次需要在类外实现,但一定是类的定义前实现,在friend函数声明前需要有函数体。
       最后在定义完友元函数后,需要先声明模板类。

2.类内实现模板类的友元函数,和普通友元函数实现一样。

C++学习第八天

标准模板库

定义:标注模板库 惠普实验室开发的一些列统称
组成部分:

容器:特殊的数据结构用来存放数据。

迭代器:用来遍历,底层是一些列的指针组成。用来通过迭代器操作容器

算法:多容器的算法操作,排序,查找等操作

空间配置器:管理模块类

配接器: 仿函数: 对象( 参数)

组件的关系:各个部分组成的关系

容器:

容器是用来存放数据的,和数据结构中的队列 双向链表栈一样

1.vector— 动态数组

  特点: 顺序存放 ,可以改变数组大小,数组在尾部添加和移除数据非常快,但是在中间或者头部添加删除数据就比较慢了。
  可以不指定大小,直接使用
 构造: 无参构造: vector<类型> 对象;
            有参构造:vector(size_t n,T x);  用n个x构建数组。
                              vector<int> v(2,10);
             拷贝构造 :vector(const T& X);
                              vector<int> x(v);

迭代器:iterator 是一种可以变量容器元素的数据类型(理解成内部类)。可以理解成容器和操作容器算法的中间。

  begin()   迭代器执行容器的首位置  
  end()       迭代器执行容器的尾位置下一个
 迭代器定义格式: vector<int>::iterator  it = arr.begin();
 成员函数:
 size()  ---  获取vector的元素个数
                  案例:vec.size() 一般用于for循环中
 clear(); 清空vector中的内容,size变成0,capacity不会变
 empty(); 判断vector是否为空,如果为空则结果为真
 insert(迭代器位置,值):在某一个位置插入值,底层会把所有位置后的数据往后移动,效率比较低下
 erase(): 删除vector的一个数据或者某一个范围的数据
           第一种:删除某一个数据   vector.erase(迭代器位置) 删除迭代器位置的元素
           第二种:删除某一个范围的数据  案例
                         vector.erase(vc.begin(),vc.begin()+4),会删除第一个到第三个元素,第四个元素不会删除;
  push_back();在尾部添加一个元素,效率比较高。参数是需要添加的值
 pop_back(); 在尾部删除一个元素,没有返回值
 capacity():得到vector的容量,可以使用的总大小。

定义一个学生的结构体,有姓名年龄,学生id。(用vector存放数据)
如果输入1则尾部添加一个学生,输入2则打印学生信息,输入3则随机位置插入学生信息,输入4则班级解散。(附加题)输入5则根据学生的id进行排序

list(双向链表)

头文件: #include<list>
特点: list是双向链表,在任意位置添加和删除都方便,效率高,但查找不容易,不可以通过下标操作。
 构造:
    第一个:空的构造函数   list()
    第二个: 拷贝构造   list(const list& other)
    第三个:list(int n,T val); 包含n个val元素
    第四个:用某一个容器的某一部分进行构造
                 list(T first,T last)  包含[ first ,last)
  成员函数: 
     首元素: front()
     尾元素: back()
     判断空:empty()
     得大小: size()
     清空: clear()
     尾部添加:push_back()
     尾部删除: pop_back()
     头部添加: push_front()
     头部删除:pop_front()
     添加元素: insert()
     交换list: swap()
     链表排序: sort()
     链表反转:  reverse() 

deque(双向队列)

 队列特点:先进先出
    deque是在vector和list的组合,可以在两端push和pop
    可以随机访问的,可以使用下标操作,等价vector中的at。
    
常用函数
  deque.assign(begin(),end())     赋值 把[begin,end)的数据赋值给deque
  deque.assign(n,val) ; 把n个val赋值给deque
  deque.at(x), 得到下标是x的数据元素,如果越界会抛出异常out_of_rang
  deque.back() ; 得到最后一个元素
   deque.front(); 得到首元素
    deque.begin()  迭代器,指向首元素
    deque.end() ;迭代器中执行尾元素
  deque.clear()  清空队列
   deque.size()  得到队列的容器
   deque.push_back(val)   尾部添加
    deque.pop_back()   尾部删除
     deque.push_front()  头部添加
     deque.rbegin();  反向迭代器,指向最后一个,it++ 指向倒数第二个
  deque.resize()  重新指定长度, 返回实际长度大小

set(集合)

 作用:叫做集合,容器中不允许有相同的数据,并且数据自动排序。  
          底层叫红黑树的平衡二叉树(红黑树) 也是一种容器
成员函数: 
   size 、clear、inert、find、swap、erase、

map(字典–地图)

特点:字典--一个键key对应一个值val,由两个部分组成。
         map则需要添加key也需要添加val所以要在map中放pair模板类
         map可以方便的查找,并且key是唯一的。
         底层是二叉树实现的。每次插入值的时候都需要重写构建一个树
pair构造:
        无参构造:std::pair<key,val> x;
        有参构造:std::pair<key ,val> x(begin,end);
         拷贝构造:std::pair<key,val> x(const std::pair<key,val>& other);       
 map的构建:
      map<string,int> xxx;
 map成员函数
     添加数据:需要添加pair模板,    insert(pair<key,val>(1,2));  insert(make_pair(1,2)); (隐式推导)
    begin():  迭代器第一元素
    end()  :迭代器最后一个元素
     at(int pos): 访问pos位置的元素
     rbegin() 反向迭代器
     erase(): 删除一个元素或者某个范围的元素,不可以使用反向迭代器
     size(): map的大小,实际存放数据元素
     find(key) 查找key对应的值,返回的是迭代器。

map中有两个成员变量,一个是first一个second ,迭代器中的first表示key,second表示val
可以通过下标访问元素。也可以通过下标赋值,但下标是key。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: PCL (Point Cloud Library) 是一个非常强大的点云处理库,它提供了丰富的功能来处理、分析和可视化点云数据。下面将从学习教程的角度来介绍PCL C++ 学习教程。 在学习PCL C++学习教程之前,我们先了解一下PCL的基础知识。PCL是一个开源项目,可以在Windows、Linux和Mac OS等操作系统上使用。它提供了常见的点云数据类型、滤波、分割、配准、特征提取和重建等功能。此外,PCL还有一个强大的可视化模块,可以方便地实时显示点云数据。 对于初学者而言,可以从PCL官方网站上的教程入手。官方网站提供了完整的文档和示例代码,可供学习者参考。此外,还有一些博客和视频教程可以帮助学习者更好地掌握PCL的使用。 学习PCL C++,需要一定的编程基础知识,例如掌握C++语言、面向对象编程和基本的算法理论等。在学习过程中,可以按照如下步骤进行: 1. 安装PCL库:根据自己的操作系统选择合适的安装方式,到PCL官方网站下载安装包并按照指南进行安装。 2. 学习PCL的基础知识:首先熟悉PCL的常见数据类型,例如点云表示和常见的PCL数据结构。然后学习如何读取和保存点云数据,以及基本的点云操作和可视化。 3. 学习PCL的模块功能:PCL库包含多个模块,例如滤波、分割、配准、特征提取和三维重建等。可以针对自己的需求选择相应的模块进行学习,并掌握它们的基本原理和使用方法。 4. 练习和实践:通过完成一些PCL的实际项目,例如点云配准、目标检测或三维重建等,来巩固所学的知识。 总之,学习PCL C++需要一定的时间和耐心,通过实践和不断学习,逐渐掌握PCL的使用技巧。希望以上简要的回答能对你理解PCL C++学习教程有所帮助。 ### 回答2: PCL(Point Cloud Library)是一个开源的计算机视觉库,主要用于处理和分析点云数据。它提供了许多功能强大的算法和工具,可以帮助开发人员在点云处理方面进行快速开发。 PCL C学习教程是PCL官方提供的一份入门教程,旨在帮助初学者快速上手PCL C的开发。该教程主要介绍了PCL的基本概念和使用方法,并提供了一些常见的点云处理算法的示例代码。通过学习PCL C学习教程,我们可以了解PCL的基本功能和使用技巧,并且可以开始进行简单的点云处理任务。 在学习PCL C的过程中,我们需要掌握一些基础知识,比如点云的表示和存储方式、点云的滤波和分割方法、点云的特征提取和匹配算法等等。PCL C学习教程提供了详细的解释和示例代码,以帮助我们理解和运用这些基础知识。 此外,PCL C学习教程还介绍了一些常见的点云处理应用场景,比如目标检测、点云配准和重建等。通过学习这些应用场景,我们可以了解到PCL C在不同领域的应用和相关的算法思想。 总结来说,PCL C学习教程是学习和使用PCL的入门指南,通过学习教程中的内容,我们可以掌握PCL C的基础知识和技能,并且开始进行简单的点云处理任务。 ### 回答3: PCL(Point Cloud Library)是一个用于点云数据处理的开源库。学习PCL可以帮助我们更好地理解和处理点云数据,从而在计算机视觉、机器人领域等方面开展相关工作。 学习PCL可以从以下几个方面入手。首先,了解PCL的基本概念和原理是非常重要的。PCL涵盖了点云数据获取、滤波、特征提取、配准等算法,了解这些基本概念是进行更高级别的数据处理的基础。 其次,学习PCL的使用方法。PCL提供了丰富的功能库和示例代码,通过实践使用可以更好地掌握PCL的各项功能。可以通过看官方文档、阅读教程、查阅论坛等方式学习PCL的使用方法。 再次,尝试应用PCL解决实际问题。通过完成一些小项目,如点云数据的滤波、分割、配准等任务,可以锻炼我们对PCL的熟练程度,并将PCL应用于实际工程中。 最后,与其他PCL爱好者交流学习。在互联网上可以找到很多与PCL相关的讨论组、社区和论坛,与其他使用PCL的人交流经验、分享学习心得,可以提高我们的学习效果。 总之,学习PCL需要理解基本概念和原理、掌握使用方法、应用于实践项目以及与他人交流学习。希望以上建议对学习PCL有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值