• 30.C++复习篇


    本章学习内容:

    • 1.const
    • 2.指针const
    • 3.inline内联函数
    • 4.函数重载
    • 5.extern “C”
    • 6.new/delete声明与释放
    • 7.namespace命名空间
    • 8.C++中的4种转换
    • 9.拷贝构造函数
    • 10.构造函数初始化列表
    • 11.析构函数
    • 12.const成员函数
    • 13.const对象
    • 14.栈、堆、静态存储区的区别
    • 15.静态成员变量/静态成员函数
    • 16.友元friend
    • 17.operator操作符重载函数
    • 18. 通过()操作符重载实现:函数对象
    • 19. 操作符重载实现:类型转换函数
    • 20.explicit显式调用(用来阻止隐式转换)
    • 21.父类和子类中的同名成员/函数
    • 22.子类对象初始化父类对象
    • 23.父类对象初始化子类对象
    • 24.纯虚函数vertual
    • 25.泛型函数模板(兼容不同类型)
    • 26.泛型类模板(兼容不同类型)
    • 27.数值型函数模板和数值型类模板(兼容不同数值)
    • 28.C++智能指针
    • 29.Qt中的智能指针

     

    1.const

    const和define宏区别

    • const常量:       由编译器处理,它会对const常量进行类型检查和作用域检查
    • define宏定义:  由预处理器处理,直接进行文本替换,不会进行各种检查

    const在C++中为真正常量.示例:

       const int c = 0;             //const局部变量
    
        int* p = (int*)&c;        //会给p重新分配空间,而c还是处于常量符号表中
    
        *p = 5;                  //此时修改的值是新的地址上,对于c而言,依旧为0
    
        printf("c = %d,*p=%d
    ", c,*p);  //打印: c = 0, *p=5

    2.指针const
    1) 底层const(位于*左侧)
    const int *p : const修饰*p为常量,也就是说该指针指向的对象内容是个常量,只能改变指向的地址.但是可以通过其他方式修改对象内容
    例如:

    int a=1,a=2; 
    const int *p = &a;
    *p = 2;         //error, 不能直接修改
    a=2;          //right,通过对象本身修改内容
    *p = &b;        //right,可以指向其它地址

    2) 顶层const(位于*右侧)
    int * const p : const修饰指针p是个常量,也就是说p指向的地址是个常量
    例如:

    int a=1,a=2;
    int * const p = &a;
    p = &b;            //error,p指向的地址是常量,永远为a地址,不能修改

    注意:顶层const变量可以替代mutable变量

    3.inline内联函数

    示例如下:

    inline int MAX(int a, int b)
    {
      return a > b ? a : b ;
    }
    • 普通函数:每次调用前,CPU都会保存现场(入栈),调用完后还要恢复现场(出栈)等额外开销.
    • 内联函数:就会在每次调用的地方,将内联函数里的代码段”内联地”展开,所以省去了额外的开销

    注意:当内联函数里的代码过多,且流程复杂时,编译器可能会拒绝该函数的内联请求,从而变成普通函数


    4.函数重载
    参数表不同主要有以下几种

    • 1) 参数个数不同
    • 2) 参数类型不同
    • 3) 参数顺序不同

    注意:

    • 重载函数需要避免使用参数默认值
    • 调用重载函数时,只会匹配函数参数表,与函数返回值无关
    • 函数重载必须发生在同一个作用域
    • 重载函数的入口地址,不能直接通过函数名来获取


    5.extern “C”
    可以实现调用C库代码.
    示例:

    #ifdef __cplusplus
    extern "C"       //通过C方式来编译add.h,也就是add()函数
    {
    #include "add.h"
    }
    #endif

    6.new/delete声明与释放

    示例如下:

    int *p = new int();  //默认值为0
    
    int *p1= new int(1); //动态分配一个int空间给p1,并赋值为1
    
    float *p2=new float(2.0f); //2.0后面加f,表示2.0是个float类型
    
    int *p3 = new int; //默认值为随机值
    
    string *p4 = new string[10];
    
    delete p;
    delete p1;
    delete p2;
    delete p3;
    delete[] p4;

    注意:
    • 释放数组的空间时,必须使用delete[],而不是delete,避免内存泄漏

     

    7.namespace命名空间

    示例:

    #include <stdio.h>
    
    namespace First //定义First命名空间
    {
      int i = 0;
    }
    
    namespace Second //定义Second命名空间
    {
      int i = 1;namespace Internal   //在Second里,再次定义一个Internal空间(实现嵌套)
      {
        struct Position
        {
          int x;
          int y;
        };
      }
    }
    
    int main()
    {
      using namespace First;         //使用First整个命名空间,成为该main()的默认空间
      using Second::Internal::Position; //使用Second->Internal空间里的Position结构体
    
      printf("First::i = %d
    ", i);
    
      printf("Second::i = %d
    ", Second::i);
    
      Position p = {2, 3}; 
      printf("p.x = %d
    ", p.x);
      printf("p.y = %d
    ", p.y);
    
      return 0;
    }

    输出结果:

    First::i = 0
    Second::i = 1
    p.x = 2
    p.y = 3


    8.C++中的4种转换
    static_cast(静态类型转换)
    用于变量和对象之间的转换,比如(bool,char,int等)
    用于有继承关系的类对象指针转换,可以通过父类对象去初始化子类对象(注意只能初始化父类的那部分)

    const_cast(去常类型转换)
    常用于去除const类对象的只读属性
    强制转换的类型必须是指针*或者引用&

    示例-去除const对象的只读属性:

    class Test 
    {
    public:
            int mval; 
            Test():mval(10)
            {
                
            }
    }; 
    
    int main()
    {
         const Test n1;
         
         //n1.mval = 100;  //error,不能直接修改常量对象的成员
         
         Test *n2 =  const_cast<Test *>(&n1);    //通过指针*转换 
         Test &n3 =  const_cast<Test &>(n1);    //通过引用&转换 
          
         n2->mval = 20;
         cout<<n1.mval<<endl;        //打印20
         
         n3.mval = 30;
         cout<<n1.mval<<endl;        //打印30
    }   

    dynamic_cast(动态类型转换)
    只能用在有虚函数的类中,一般在多重继承下用的比较多,比如:

    class BaseA
    {
    public:
      virtual void funcA()
      {
        cout<<"BaseA: funcA()"<<endl;
      }
    };
    
    class BaseB
    {
    public:
      virtual void funcB()
      {
        cout<<"BaseB: funcB()"<<endl;
      }
    };
    
    class Derived : public BaseA,public BaseB
    { 
    };
    
    int main()
    {
      Derived d;
      BaseA *pa=&d; 
      
      pa->funcA(); //打印 BaseA: funcA()
    
      /*通过强制转换执行*/
      BaseB *pb=(BaseB *)pa; 
      pb->funcB(); //还是打印BaseA: funcA(), 因为pb还是指向pa,执行的还是pa的虚函数表 
    
    
      /*通过dynamic_cast执行*/
      pb = dynamic_cast<BaseB *>(pa); 
      pb->funcB(); //打印 BaseB: funcB()
      //编译器会去检测pa所在的地址,发现有多个虚函数表,然后根据 <BaseB *>来修正指针pb 
      return 0; 
    }

    reinterpret_cast(解读类型转换)
    对要转换的数据重新进行解读,适用于所有指针的强制转换

     

    9.拷贝构造函数
    一般用于当类对象中有成员指针时,才会自己写拷贝构造函数,因为编译器自带的默认拷贝构造函数只支持浅拷贝

    class Test
    {
       //... ...
        Test(const Test& t)
        {
          //copy... ...
        }
    };

    10.构造函数初始化列表

    • 当类中有const成员变量时,则必须要用初始化列表进行初始化.
    • 对于其它普通变量如果不初始化的话则为随机值.
    • 初始化列表位于构造函数名右侧,以一个冒号开始,接着便是需要初始化的变量,以逗号隔开

     示例如下:

    class Example
    {
    private:
        int i;
        float j;
        const int ci;
        int *p;
    public:
         Test(): j(1.5),i(2),ci(10),p(new int(3))    //初始化i=2,j=1.5,ci=10 *p=3
        {
        }
    };


    11.析构函数
    注意:

    • 在类里,当定义了析构函数,编译器就不会提供默认的构造函数了,所以还要自己定义一个构造函数
    • 使用new创建的对象变量,在不使用时,需要使用delete,才能调用析构函数

    构造函数的调用顺序

    • 1. 首先判断父类是否存在,若存在则调用父类构造函数
    • 2. 其次判断该对象的是否有类成员,若有则调用类成员的构造函数(调用顺序按照声明的顺序来构造)
    • 3. 最后调用对象本身的构造函数

    12.const成员函数

    • cosnt成员函数里只能调用const成员函数
    • const成员函数中不能直接修改成员变量的值
    • 只有对mutable成员变量或者顶层const成员是可以修改的
    • 如果用const修饰的函数,那么该函数一定是类的成员函数

    13.const对象

    • const对象的成员变量不允许被改变,
    • const对象只能调用const成员函数,而非const对象可以访问const成员函数
    • const对象是编译阶段的概念,运行时无效
    • const对象可以通过const_cast强制转换来实现改变其中成员变量的值

    14.栈、堆、静态存储区的区别

    用来存放函数里的局部变量,当调用某个函数时(执行某个代码段),会将该函数的变量(从数据段读出)入栈,然后退出函数的时候,会将该局部变量出栈进行销毁.
    一般如果局部变量未初始化的话,都是随机值

    堆由程序员分配释放new/delete,所以需要注意内存泄漏问题
    一般new分配的对象变量,其成员都是随机值
    静态存储区
    用来存放全局变量,一直会存在的,一般编译器为自动将未赋值的全局变量进行一次清0

    15.静态成员变量/静态成员函数

    •  在类里定义时直接通过static关键字修饰
    •  静态成员变量需要在类外单独分配空间,而静态成员函数则不需要
    •  静态成员变量在程序内部位于静态存储区
    •  对于public公有的静态成员变量/函数时,可以直接通过类名进行直接访问
    •  静态成员函数中不能访问非静态成员变量,因为它属于整个类的,没有隐含this指针

    示例如下:

    class Test{
    
    private:
      static int mval;
    
    public: 
      Test()
      {
        print(); 
      } 
    
      static int print()    //静态成员函数是存在代码段中,所以不在类外定义也可以 
      {
        cout<<"mval="<<mval<<endl; 
      } 
    }; 
    int Test::mval=4; //静态成员变量存在静态存储区中,所以需要在类外定义 int main() {   Test::print(); //通过类名直接访问静态成员函数,打印: mval=4 }


    16.友元friend

    • 友元的好处在于,方便快捷.可以通过friend函数来访问这个类的私有成员
    • 友元的坏处在于,破坏了面向对象的封装性,在现代已经逐渐被遗弃

    示例:

    #include "stdio.h"
    
    class Test{
    private:
      static int n;
      int x;
      int y;
    
    public:
      Test(int x,int y)
      {
        this->x = x;
        this->y = y;
      } 
    
      friend void f_func(const Test& t); //声明Test的友元是f_func()函数
    
    };
    
    int Test::n = 3; 
    
    void f_func(const Test& t)
    {
      printf("t.x=%d
    ",t.x); 
      printf("t.y=%d
    ",t.y);
      printf("t.n=%d
    ",t.n); //访问私有静态成员变量
    }
    
    int main()
    {
      Test t1(1,2);
      f_func(t1);
      return 0;
    } 

    17.operator操作符重载函数

    使'+,-,*,/'等操作符拥有了重载能力,能够实现对象之间的操作,而不再单纯的普通变量之间的操作了.

    示例如下,实现一个加法类:

    class Add
    {
        double mval;
    public:
        explicit Add(double t=0)
        {
            mval =  t; 
        }
         
        Add& operator +(const Add& t) //实现相同类对象相加
        {
             this->mval += t.mval;
             cout<<"operator +(const Add& t)"<<endl;
             return *this;            //返回该对象,表示可以重复使用 
        }
    Add
    & operator +(int i) //实现int型对象相加 { this->mval += i; cout<<"operator +(int i)"<<endl; return *this; } Add& operator +(double d) //实现double型对象相加 { this->mval += d; cout<<"operator +(double d)"<<endl; return *this; } Add& operator = (const Add& t) //重载赋值操作符 { cout<<"operator =(const Add& t)"<<endl; if(this!=&t) { mval = t.mval; } return *this; } double val() { return mval; } };
    int main() { Add a1(11.5); Add a2(1.25); a1=a1+a2; //相当于调用两步: a1.operator =(a1.operator +(a2)); cout<< a1.val() <<endl; }

    运行打印:


    18.通过()操作符重载实现:函数对象

    • 函数对象是指该对象具备函数的行为
    • 函数对象,是通过()调用操作符声明得到的,然后便能通过函数方式来调用该对象了.
    • ()调用操作符可以定义不同参数的多个重载函数
    • ()调用操作符只能通过类的成员函数重载(不能通过全局函数)

    示例:

    class Test{
    public:
      void operator () (void) //通过()重载操作符,来使对象具备函数的行为
      {
        cout<<"hello"<<endl;
      }
    };
    
    int main()
    {
      Test t;
      t(); //来调用t这个函数对象打印"hello"
    } 

    PS:好处在于可以封装自己的成员以及其它函数,所以能够更好的面向对象.

    19.操作符重载实现:类型转换函数
    示例如下:

    class Test{
    
      int mValue;
    
    public:
      Test(int i=0)
      {
        mValue=i;
      }
    
      operator int()   //重载int类型
      {
        return mValue;
      }
    };
    
    int main()
    {
      Test t(1000);
      int i=t;     //等价于: i=t.operator int();
      cout<<i<<endl; //i=1000
    }

    20.explicit显式调用(用来阻止隐式转换)
    示例:

    class Test{
    public:
      explicit Test(unsigned int i)
      {
        cout<<"unsigned i= "<<i<<endl;
      } 
    };
    
    int main()
    { 
      short num=3;
      //Test t1=num; //Error,因为explicit阻止short类型 转换为unsigned int 类型
    
      /*只能有以下3个方法实现*/
      Test t2=(Test)num;         //C方式强制转换,不推荐
      Test t3=static_cast<Test>(num); //C++方式强制转换
      Test t4(num);            //手工调用构造函数
      return 0;
    }

    21.父类和子类中的同名成员/函数

    • 子类可以定义父类中的同名成员和同名函数
    • 子类中的成员变量和函数将会隐藏父类的同名成员变量和函数
    • 父类中的同名成员变量和函数依然存在子类中
    • 通过作用域分辨符(::)才可以访问父类中的同名成员变量和函数

    示例1-通过子类访问父类同名函数和同名成员:

    class Parent{
    
    public:
    
      int mval;
    
      Parent():mval(100)
      { }
    
      void print()
      {
      cout<<"Parent: mval="<<mval<<endl;
      }
    
    };
    
    class Child :public Parent
    {
      public:
      int mval;
    
      Child():mval(20)
      { }
    
      void print()
      {
      cout<<"Child: mval="<<mval<<endl;
      }    
    };
    
    
    int main()
    {
      Child c;
      c.Parent::print();          //调用父类的同名成员函数 
      cout<<c.mval<<endl;
      cout<<c.Parent::mval<<endl;    //打印父类的同名成员变量
    }

    22.子类对象初始化父类对象
    以上示例的Parent类Child类为例,在编译器中,可以将子类对象退化为父类对象,从而实现子类来初始化父类,比如:

    Parent p1(Child());    //Child()构造函数会返回一个临时对象,从而通过子类初始化父类
    Child c;
    Parent & p2 = c ;    //定义p2是C对象的别名

    23.父类对象初始化子类对象
    只能使用static_cast或者C方式转换,以上示例的Parent类和Child类为例:

    Parent p;
    Child *c = static_cast<Child *>(&p);

    24.纯虚函数vertual

    • 在父类中用virtual声明的成员函数便为虚函数
    • 虚函数的作用在于,能够正确调用某个同名函数是哪个类的对象
    • 比如:当某个子类被强制转换为父类时,则父类的虚函数也会被替代为子类的,从而实现程序灵活性

    一个典型的示例,如下所示:

    class Base    //父类
    {
    public:
      virtual void func()    //声明func为虚函数
      {
        cout<<"Base: func()"<<endl;
      }
    };
    
    class BaseA : public Base //子类A
    {
    public:
      void func()
      {
        cout<<"BaseA: funcA()"<<endl;
      }
    };
    
    class BaseB : public Base //子类B
    {
    public:
      void func()
      {
        cout<<"BaseB: funcB()"<<endl;
      }
    };
    
    void print(class Base& b)
    {
      b.func();
    }
    
    int main()
    {
      BaseA bA;
      BaseB bB;
      print(bA);    
      print(bB);
      return 0; 
    }

    运行打印:

    如上图可以看到,我们以print(bA)为例:
    再调用print()函数时,会将BaseA bA转换为父类Base,由于父类Base有个func()虚函数,所以会被动态替换为bA子类的func()函数.所以会打印funcA()

    如果将上面代码virtual void func()改为void func()重新编译运行后,打印:

    如上图可以看到,没有虚函数后,整个代码都变得没有灵活性,不适合类的扩展.

    PS:在QT中,virtual用的非常多,比如QWidget的showEvent函数:

    virtual void    showEvent ( QShowEvent * event );

    假如我们需要在窗口显示时加点特效时,只需要重写它即可,而QT库只需要根据vertual特性来自动调用我们重写的函数,非常灵活.

     

    25.泛型函数模板(兼容不同类型)


    函数模板是C++中重要的代码复用方式, 可通过不同类型进行调用

    • 通过template关键字来声明使用模板
    • 通过typename关键字来定义模板类型

    示例:

    template <typename T> //声明使用模板,并定义T是一个模板类型
    void Swap(T& a, T& b) //紧接着使用T
    {
      T c = a;
      a = b;
      b = c;
    } 
    int main() {   int a=0;   int b=1;   Swap(a,b);       //自动调用,编译器根据a和b的类型来推导   float c=0;   float d=1;   Swap<float>(c,d); //显示调用,指定T是float类型 }

    为什么函数模板能够执行不同的类型参数?
    答:

    • 其实编译器对函数模板进行了两次编译
    • 第一次编译时,首先去检查函数模板本身有没有语法错误
    • 第二次编译时,会去找调用函数模板的代码,然后通过代码的真正参数,来生成真正的函数。
    • 所以函数模板,其实只是一个模具,当我们调用它时,编译器就会给我们生成真正的函数.

    函数模板也支持多参数,示例如下(如果定义了返回值模板,则必须要显示指定返回值类型,因为编译器不知道到底返回什么类型):

    #include <iostream>
    
    using namespace std;
    
    template<typename T1,typename T2,typename T3> 
    T1 Add(T2 a,T3 b)
    {
    return static_cast<T1>(a+b); 
    }
    
    int main()
    {
    // int a = add(1,1.5);       //该行编译出错,没有指定返回值类型
    int a = Add<int>(1,1.5);      //指定T1为int形    
    cout<<a<<endl;           //打印2
    
    float b = Add<float,int,float>(1,1.5); //指定T1,T2,T3类型
    cout<<b<<endl;           //2.5
    
    return 0;
    }

    26.泛型类模板(兼容不同类型)
    类模板和函数模板一样,都是进行2次编译,需要注意的是定义对象必须显示指定所有类型
    示例:

    template<typename t1,typename t2,typename t3>
    class Operator{
    
    public:
      t1 add(t2 num1,t3 num2)
      {
        return num1+num2;
      }
    };
    int main() {   Operator<float,int,float>t;   cout<<t.add(11,11.5)<<endl; //11+11.5 = 22.5   return 0; }

    27.数值型函数模板和数值型类模板(兼容不同数值)
    数值型和泛型类似,但是数值型模板必须在编译时被唯一确定

    示例1-数值型函数模板:

    template <typename T,int N >    //定义一个泛型值T,还有个int型的数值 
    void func()
    {
      T arr[N];    //使用模板参数T和N定义局部数组 
    }
    
    int main()
    {
      func<int,10>(); //相当于实现 int arr[10] 
    }

    示例2-数值型类模板(实现1+2+3+....+N值):

    template < int N >
    class Sum
    {
    public:
      static const int VALUE = Sum<N-1>::VALUE + N; //通过Sum<N-1>::VALUE实现递归调用,并返回该临时对象 
    };
    
    template < >          //完全特化,因为我们知道N为1,所以不需要写< int N >    
    class Sum < 1 >        //重载Sum类(类似于函数重载),当N==1时调用该类 
    {
    public:
      static const int VALUE = 1;
    };
    
    int main()
    {
      cout << "1 + 2 + 3 + ... + 10 = " << Sum<10>::VALUE << endl;
      cout << "1 + 2 + 3 + ... + 100 = " << Sum<100>::VALUE << endl;
      return 0;
    }

    28.C++智能指针
    头文件<memory>
    1)auto_ptr

    • 生命周期结束时,自动摧毁指向的内存空间
    • 不能指向堆数组(因为auto_ptr的析构函数删除指针用的是delete,而不是delete[])
    • auto_ptr的构造函数为explicit类型,所以只能显示初始化
    • 提供get()成员函数,可以用来查看类里的指针地址
    • 一个堆空间永远只属于一个对象(比如auto_ptr被拷贝/赋值,则自身的指针指向的地址会被抢占)

    示例如下:

    #include <iostream>
    #include <memory>
    
    using namespace std;
    
    class Test{
      int mvalue;
    public:
      Test(int i=0)
      {
        mvalue = i ;
        cout<< "Test("<<mvalue<<")"<<endl;    
      }
      ~Test()
      {
        cout<< "~Test("<<mvalue<<")"<<endl;    
      }
    };
    
    int main()
    {
      auto_ptr<Test> p1(new Test(1));
      auto_ptr<Test> p2(new Test(2));
    
      cout<<"p1: addr="<<p1.get()<<endl;    
      cout<<"p2: addr="<<p2.get()<<endl;    
    
      p2 = p1;
      cout<<"p1: addr="<<p1.get()<<endl;    
      cout<<"p2: addr="<<p2.get()<<endl;     
      return 0;
    }

    运行打印:

    如上图所示,当我们执行p2=p1后,便执行了p2的析构函数进行自动释放了.并且p1.get()=0,所以auto_ptr具备自动释放功能以及同块堆空间下只能有一个指针对象特性

    2) shared_ptr (需要C++11支持)

    • 带有引用计数机制,支持多个指针对象指向同一片内存(实现共享)
    • 提供swap()成员函数,用来交换两个相同类型的对象指针地址
    • 提供unique()成员函数, 判断该指针对象地址是否被其它指针对象引用
    • 提供get()成员函数,用来获取指针对象指向的地址
    • 提供reset()成员函数,将自身指针对象地址设为NULL,并将引用计数-1(当计数为0,会自动去delete内存)
    • 提供use_count()成员函数,可以用来查看引用计数个数

    示例如下所示:

    class Test{
    
    public:
      int mvalue;
      Test(int i=0)
      {
        mvalue = i ;
        cout<< "Test("<<mvalue<<")"<<endl;    
      }
      ~Test()
      {
        cout<< "~Test("<<mvalue<<")"<<endl;    
      }
    };
    
    int main()
    {
      shared_ptr<Test> p1(new Test(1));
      shared_ptr<Test> p2(new Test(2));
    
      cout<<"p1: addr="<<p1.get()<<endl;    
      cout<<"p2: addr="<<p2.get()<<endl;    
    
      p1.swap(p2);    //互换p1和p2指针指向的地址 
    
      cout<<"p1: addr="<<p1.get()<<endl;    
      cout<<"p2: addr="<<p2.get()<<endl;
    
      p1 = p2;    //使p1指向p2指向的地址,并且释放p1之前指向的地址 
      cout<<"p1:addr="<<p1.get()<<", p2:addr="<<p2.get()<<endl;    
      cout<<"p1: unique="<<p1.unique()<<endl;    //p1和p2指向同一片内存,所以为0 
      cout<<"p1: count="<<p1.use_count()<<endl;
      return 0;
    }

    运行打印:

    29.Qt中的智能指针
    -QPointer

    • 当其指向的对象被销毁时,本身会自动赋值为NULL(从而避免被多次释放和野指针)
    • 缺点在于,该模板类析构时,不会自动摧毁所指向的对象(需要手工delete)

    -QSharedPointer

    • 带有引用计数机制,支持多个指针对象指向同一片内存(实现共享)
    • 可以被自由地拷贝和赋值
    • 当引用计数为0(最后一个指针被摧毁)时,才删除指向的对象(和shared_ptr类似)

    -QScopedPointer

    • 优点在于生命期结束后会自动删除它所指的对象(不需要手工delete)
    • 不支持多个QScopedPointer指针对象指向同一片内存(不能共享)

    示例:

    QScopedPointer<QPushButton> p1(new QPushButton);
  • 相关阅读:
    Day05_java方法 方法
    Day05_java流程控制 break、continue
    Day05_java流程控制 循环结构
    Day05_java流程控制 switch选择结构
    Day05_java流程控制结构
    Day04_java流程控制 顺序结构
    Day04_java流程控制 用户交换Scanner
    Day03_java基础 JavaDoc生成文档
    Day03_java基础 包机制
    ltib学习抄录
  • 原文地址:https://www.cnblogs.com/lifexy/p/10383993.html
Copyright © 2020-2023  润新知