类的设计(1)

1.头文件和类的声明

写头文件的基本格式的用处:

  1. 防卫式的声明
    • ifndef _ COMPLEX_ :如果没有定义_ COMPLEX_这个变量,就进入这个头文件。相当与第一次进入过后第二次就不会进入,这样就不会有重复的引入库的操作。
  2. 一个类代表有一个头文件
    • 便于管理
# ifndef _COMPLEX_
# define _COMPLEX_

````
class ostream;
class complex;//类的前置声明

class complex
{
    //类的本体
}
    
complex::function...
{
    //类的外部定义成员函数,其函数也需要在类的本体进行声明
}
    
//全局函数和友元函数的定义
    
    
    
````
# endif

2.Class的说明

2.1 模板类的格式

为什么要用模板类?

class complex
{
public:
    complex(int r = 0, int i = 0) :re(r), im(i)
    {}
    complex& operator += (const complex);
    int real() const { return re; }
    int imag() const { return im; }
private:
    int re, im;
    friend complex _doapl(complex*, const complex&);
};
class complex
{
public:
    complex(float r = 0, float i = 0) :re(r), im(i)
    {}
    complex& operator += (const complex);
    float real() const { return re; }
    float imag() const { return im; }
private:
   float re, im;
    friend complex _doapl(complex*, const complex&);
};

观察上面两种类的,会发现只有类里面的某些数据类型不一样,其相应的功能操作都是一样的,就因为数据类型不一样导致要多写几个重复操作而数据类型不一样的类,于是就有了类模板的出现

template<typename T>   //T 可以int、float、double,等使用的时候再来选择,不局限于某种类型;也就是当我们定义						//变量的为其指定相对应的类型
class complex
{
public:
    complex(T r = 0, T i = 0) :re(r), im(i)
    {}
    complex& operator += (const complex);
    T real() const { return re; }
    T imag() const { return im; }
private:
   T re, im;
    friend complex _doapl(complex*, const complex&);
};


int main()
{
    complex<double>c1(2.3,2.5);
    complex<int>c2(1,2);
}

2.2 class的说明

  1. class的方法在类里面直接定义(如果函数很简单编译会将其变成inline函数)

  2. class的方法在类里面声明在类外定义

  3. 定义在类内的是自动inline

  4. 是否加inline标识符

    • inline只适合函数体内代码简单的函数使用,不能包含复杂的结构控制语句,比如while,switch,不能是递归函数(自己调用自己),inline函数可以提高执行效率,但不可以将所有的函数定义成内联函数

    • inline 仅仅是一个对编译器的建议,所以最后能否真正内联,看编译器的意思,他如果认为函数不复杂,就会在调用点展开,就会真正内联,并不是声明了inline就是inline

    • inline函数尽量放在头文件中

    • 写法:inline必须与函数定义体放在一起才能使函数成为内联

  5. 一般类里面会出现三大块,public;private; protect

  6. 访问级别

    • 不可访问private的数据或者函数
    • 可通过public方法来访问private的数据或者函数
  7. 构造函数

    • 当我们创建对象时,构造函数自动就会被调用

    • 动态创建对象(new),返回的是一个指针

    • 当构造函数有指明默认的参数时,如果创建对象时没有指明参数,就用默认的参数为准;创建对象时有指明参数时,以指明的参数为准,为其类里面的数据赋值。

    • 强调不是只有构造函数才有默认参数这一特性,其他函数也可以有默认参数值

    • 没有返回类型

    • 写法有点不一致,至于构造函数还需要做其他的操作就可以在{}进行编写,允许把初始化放在{}里面,但不建议

      class complex
      {
      public:
          complex(T r = 0, T i = 0) :re(r), im(i)//初始化
          {} //其他操作
      };
      
    • 构造函数可以有很多个(重载),重载只是函数名相同,但是有可能时参数类型不一样,或者有参数和无参数的区别,但是对于编译器来说它确实不一样的函数,具体调用谁由编译器决定

      • 函数重载同名是可以同时存在的,看起来是同名的,但对于编译器来说是不同名。
      • 函数重载通常发生在构造函数里面
      • 函数重载必须有不同点,不然两者都可以调用就导致冲突,给编译器造成混淆。
    • 构造函数一般情况下不放在private块里面,但是如果不允许被外界创建对象的话就可以把构造函数放在private里面,针对于把构造函数放在private在设计模式中singleton用到了这种。

  8. 常量成员函数

    • class里面的函数分为会改变对象数据的和不会改变对象数据的函数,其中不会改变就加上const(只要不改变一定加上)

      double real() const {return re;}//注意中间的const
      
    • 还有一种方式说明类创建对象的时候里面的数据是不允许改动的,在创建对象的前面加const


class complex
{
public:
    complex(int r = 0, int i = 0) :re(r), im(i)
    {}
    complex& operator += (const complex);
    int real()  { return re; }
    int imag()  { return im; }			//函数没有const代表这个函数可能会改变它
private:
    int re, im;
    friend complex _doapl(complex*, const complex&);
};


const complex c1(1, 2);//创建对象,前面加了const只能调用不能改变数据内容的函数
cout<<c1.real();
cout<<c1.imag();//假设上面的函数没添加const,这样去调用就会报错,因为函数没有const代表这个函数可能会改变它,而				//创建对象的时候const又说明不能改变它,编译器运行时就会矛盾导致报错


  1. 析构函数,类一般分为带指针和不带指针的,不带指针的类多数不用编写析构函数

  2. 参数传递是值传递还是引用(其中引用是否带有const),指针传递

    • 用值传递,复制变量的成本很大(不建议使用),小数据使用还行。
    • 指针传递,复制成本要小很多(只需要复制地址)但是读写成本高(首先访问这个指针,获取它内部包含的数据;然后根据得到的地址并访问相对应的地址来获取数据)
    • 引用传递,(引用的概念:在底部来说引用就是一个指针,传引用相当于传指针)速度很快(建议用这种方式)
    • const 引用传递,既要速度很快又不要调用的函数改动,const 引用最合适
    • 本着一个原则首先考虑引用其次不满足再考虑其他
  3. friend(友元)

    • 友元函数可以取得class的private的数据成员
    • 友元函数在class里面声明,类外定义,声明时需要在函数名前加friend
    • 可以将其他的类成员函数声明成友元函数,但是不能把其他的私有成员函数声明成友元函数
  4. 返回引用的函数

    • 只要是返回引用都可以当作左值使用

    • 不要返回局部对象的引用

    • 返回this指向的对象,在类的成员函数中,返回引用的类对象,当然不能是函数内定义的类对象(会释放掉)

    • 返回 * this ,返回的是对象本身而不是对象的副本(this是指针,*this是对象),作为左值改变对象本身

  5. 相同class的各个object互为友元

    class complex
    {
    public:
        complex(double r = 0,double i =0):re(r),im(i){}
        int func(const complex &param)
        {
            return param.re+param.im;
    }
    private:
        double re,im;
        
    }
    
    
    int main()
    {
        complex c1(1,3);
        comple c2;
        c2.func(c1); //至于这里为什么成立,会发现是两个对象为何能访问了另一个对象的私有成员?是相同class的各个					//object互为友元,有这个特性是成立的但是并没有打破封装的特性
        				//可以发现func函数的形参有const,说明不能改变c1的值,但是改变的是c2的值
    }
    
  6. class外面的各种定义

    • 函数经过运算过后结果可以存放已有的变量(指的是形参)上进行返回,也可以是再创建一个新的变量进行存储再返回
  7. 可变数据成员(mutable关键字)

    • 通过在变量声明中加入mutable关键字中,代表它是可变数据成员,永远都不会是const,尽管是在const的函数也是可以改变值的,这是mutable关键字的特性
  8. 操作符重载-成员函数–有this

    • 在操作符前面加关键字operator

    • 重新定义操作符的功能(比如说+,一般默认为数字的相加,但是对+进行重新定义的话,两个树或者两个石头也可以相加,看自己需求)

    • 任何类成员函数都会隐藏一个this 指针并指向调用者

    • this不会写在形参里面,但是可在成员函数里面用

  9. 操作符重载-非成员函数–无this

    inline complex 
        operator + (const complex &x,const complex &y)
    {
        return complex(real(x)+real(y),imag(x)+imag(y));//相加的结果是放在了在这个函数内创建的局部变量,											//传出去过后这个局部变量就释放了,所以说不可能是返回引用
    }
    
    inline complex& _doapl(complex *ths,const complex& r)
    {
        return *ths;
    }
    
    • 特殊语法(临时对象): typename( c );内部也可以加减,相当于定义了一个新的该类型变量并赋值。它的生命到下一行就会结束,并且没有名称。
    • 不能返回引用,这里的操作符二者不是一个加到另外一个上面,是两个对象相加,因此返回的必须是一个对象。
  10. return value和return reference

    //2.return by reference
    inline complex& //引用接收 提高效率 还有一个重要的知识点 以备调用者 调用c3 += c2 += c1;
    __doapl(complex* ths, const complex& r)
    {
        ths->re += r.re;
        ths->im += r.im;
        return *ths; //返回的是对象, 接收却是引用,   这是C++的一个重要知识点,传递者无需知道接收者以什么形式接收
    }
    
    //认识成员函数都有一个this point 指向调用者
    //+=的完整形式应该是这样,谁调用这个函数谁就是this
    inline complex&
    complex::operator+=(const complex& r)
    {
        return __dopal(this, r);
    }
    
    //调试
    c3 += c2 += c1;
    
    
    • 以引用或者value接收都可以,只不过引用接收效率快
    • 开始的complex不能为void,如果只是c2+=c1,这样的操作是可以的,c2的值得到了改变,但是如果c3+=c2+=c1这样的操作就会出错,因为c1和c2加完后没有返回值,无法继续和c3相加。
  11. 注意一下**<<**的重载:" << "输出符号的重载,只能作为全局函数来定义。"<<“的类型是"ostream”;其他操作符可以考虑成员函数的写法和非成员函数的写法

    • 首先是第一个形参是一个非常量ostream的对象的引用(向流写入内容会改变其状态,是一个改变量,其次为何是引用,是因为我们无法复制ostream对象)
    • 第二个形参是一个常量的引用,该常量是我们想要打印的类型(1,避免复制实参,2打印对象一般不会改变对象内容)
    • 返回 ostream 类对象的引用,是为了能够连续读取复数(可以返会void,但不能连续读取)
inline double imag(const complex& x)
{
    return x.imag();//打印虚部
}


inline double real(const complex& x)
{
    return x.real();//打印实部
}


inline complex operator + (const complex& x)
{
    return x;//重载+,这里所表示的是正号,如何与真正的+相区分,是通过参数的个数来区别的,重载必须有点不一样的点
}

inline complex operator - (const complex& x)
{
    return complex(-real(x), -imag(x));
}
//以全局函数形式重载<<
ostream& operator <<(ostream& os, const complex& x)//返回不能用void,因为有可能连续调用<<
{
    return os << real(x) << " " << imag(x) << endl;
}

  1. 重载>>运算符
    • 第一个形参是读取流的引用(istream &is)
    • 第二个形参是将要读入到非常量对象的引用
    • 返回 istream 类对象的引用,是为了能够连续读取复数(可以返会void,但不能连续读取)

class complex
{
public:
    complex(int r = 0, int i = 0) :re(r), im(i)
    {}
    complex& operator += (const complex& );
    int real() const { return re; }
    int imag() const { return im; }

private:
    int re, im;
    friend complex& _doapl(complex*, const complex&);
    friend istream& operator >>(istream& is, complex& x);//声明友元
};

inline complex& _doapl(complex* ths, const complex& r)
{
    ths->re += r.re;
    ths->im += r.im;
  
    return *ths;
}
//以全局函数形式重载>>,istream 表示输入流,cin 是 istream 类的对象,只不过这个对象是在标准库中定义的,。之所以返回 istream 类对象的引用,是为了能够连续读取复数,让代码书写更加漂亮.
//因为输入流需要把数据给complex的私有数据,就需要将其设为该类的友元函数(对象或者是不能读取私有成员)
istream& operator >>(istream& is, complex& x)
{
    is >> x.re >> x.im;
    return is;
}

//测试
	complex c4;
	cin >> c4;
	cout << c4.imag() << " " << c4.real() << endl;

2.3总结:

  1. 在头文件进行防卫式的常数定义

  2. 首先class的声明

  3. 构造函数的设计

  4. 成员函数该不该加const

  5. 参数的传递尽量用引用,加不加const看需求

  6. return的 by value还是by reference

  7. 数据放在private块,函数放在public

  8. 类的对象不能访问私有成员,可通过成员函数或者声明成友元函数来访问

  9. 重载<<,>>必须是非成员函数,至于定不定义成友元函数看需求

  10. inline的使用限制:

  11. 其函数是否定义成员函数看其判断(比如重载+,如果只是复数+复数就将其定义成员函数,没办法应付复数+double的类型的+)

3.三大函数:拷贝构造、拷贝复制、析构函数

  1. string类里面是存在指针的----有指针代表的类必须要有析构函数(不带指针的类,其拷贝构造函数可以用编译器自带的功能),但是类里面所涉及到的指针必须重写----string为什么用指针(是因为string的对象一般是一长串的)

  2. 字符串就是一个指针指向头,最后有一个结束符号, 对于含有指针的类中,过程中做了动态分配,占用了动态内存,不会随着函数结束而被清理,所以需要单独清理该内存,防止内存泄漏。

    
    inline string::string(const char* cstr = 0)//构造函数
    {
        if(cstr)							//带初始值
        {
            m_data = new char[strlen(cstr)+1];
            strcpy (m_data, cstr);//如果构造函数已赋值,则先分配一个大小为所赋值字符串的长度的内存加1,最后的1用来存结束符号。然后将所赋值cstr拷贝到m_data中。
        }
        else								//未指定初始值
        {
            m_data = new char[1];
            *m_data = '\0';//如果构造函数未赋值,则自动分配一个大小为1的内存用来存结束符号
        }
    }
    
    inline
    string::~string()
    {
        delete[] m_data;//析构函数,清理,注意delete[]的写法
    }
    
    
    //main.cpp
    string s1();
    string s2("hello");
    string *p = new string ("hello"); //动态创建对象
    delete p;//用完过后必须及时销毁它
    
  3. 在我们设计带类的指针,有以上的不带指针的类的基础上,其拷贝构造函数、拷贝赋值函数、析构函数必须要写;

    • 为什么要写拷贝赋值函数
    string a("hello");
    string b("world");
    b=a;//这时程序非常危险,a里面有指针指向hello,b里面有指针指向world,经过赋值操作过后b和a里面的指针同时指向hello,b原来指向的地址成为野内容(所占的空间不会被收回),导致内存泄漏;释放其中一个指针(a或者b)就会导致另一个指针所指向的空间被释放成为野指针
    
    //我们目的是b和a的地址不同但具有相同的内容
    

    拷贝赋值函数的编写—检测是否自我赋值or赋值

    //经典写法
    
    inline string& string::operator = (const string& str)//拷贝赋值
    {
        if(this == &str){ return *this;}//检测是不是自我赋值,这一步非常重要,如果不写,当使用者自我赋值时会出错,看下图
       
         //操作步骤
        delete[] m_data;//1.先清掉自己,没有上面的检测,自我赋值就会导致野指针
        m_data = new char[ strlen(str.m_data)+1];//2.构造与右边值相同大小的空间
        strcpy(m_data, str.m_data);//3.把左边值复制进来
        return *this;
    }
    {
        string s1("hello");
        s2=s1;//拷贝赋值
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cxNEYYsW-1692757106961)(image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0dFTkdYSU5HR1VBTkc=,size_16,color_FFFFFF,t_70)]

    • 拷贝构造函数

      //如果不写拷贝函数,所使用的编译器就是调用默认的的,称为浅拷贝就会导致内存泄漏或者两个指针指向同一块内容
      inline
      string::string(const string& str)
      {
          m_data = new char[ strlen(str.m_data)+1];//深拷贝,创造一片空间容纳蓝本
          strcpy(m_data, str.m_data);//将内容拷贝进内存
      }
      
      {
          string s1("hello");//构造函数
          string s2(s1);//拷贝构造
          s2=s1;//拷贝赋值
      }
      
      

4.堆(heap)、栈(stack)与内存管理

4.1 堆的定义和栈的定义

heap(堆):就是通过new、malloc、realloc分配的内存块,编译器不会负责它们的释放工作,需要用程序区释放。分配方式类似于数据结构中的链表。“内存泄漏”通常说的就是堆区

stack(栈):存放函数的参数值、局部变量等,由编译器自动分配和释放,通常在函数执行完后就释放了,其操作方式类似于数据结构中的栈。栈内存分配运算内置于CPU的指令集,效率很高,但是分配的内存量有限,比如iOS中栈区的大小是2M

class Complex{....};

{
    Complex c1(1,2);//存储在栈中,c1的存储会随着函数结束而消失,这种作用域的对象称为auto object,可以被自动清理
    Complex *p = new Complex(3);//动态获得堆的内存,因此不会随着函数结束而消失,必须手动delete掉.static object

}

4.2 stack的生命周期

  • stack objects:离开作用域就会消失,又称为auto object(指其析构函数会自动调用)

  • static local objects:生命在作用域结束之后仍然存在,直到程序结束之后才会调用析构函数

  • global objects: 全局对象,在{}之外定义的对象,生命同样在整个程序结束之后消失。

{
    Complex c1(1,2);//stack object
    static Complex c2(1,2);//static object
}

Complex m(1,2)//global object

int main()
{
    ...
    return 0;
}

4.3 heap的生命周期

class Complex {...};

{
    Complex* p = new Complex;
    ....
    delete p;
}
//p所指的便是heap object, 其生命在它被delete之后结束

//VS

{
    Complex* p = new Complex;
    ....
}
//以上出现内存泄漏,因为当作用域结束,p所指的heap object仍然存在,但指针p的生命却结束了,没办法控制这个区域的内存,作用域之外再也看不到p了,因此没办法再delete p了。

5.new和delete的过程

new:先分配空间,再调用构造函数

complex *p  = new complex(1,2);

//上述一条语句转换成编译器的工作如下
void *mem = operator new(sizeof (complex));  //先分配空间
p = static_cast <complex *>(mem);			//强制转换
p->complex::complex(1,2)					//调用构造函数,这里的p其实就是this

delete:先调用析构函数,再释放内存

delete p;
//上述一条语句转换成编译器的工作如下
complex::~complex(p);		//调用
operator delete(p)			//释放内存,这里的operator是c++的一个特殊函数,其内部调用free(p)

6.动态分配array,到底有多大

  • 调试状态下:存储一个数据的内存大小是:数据本身大小+上下cookie+灰色填充(结果不是16的倍数时要向下分配内存直至满足16的倍数)
  • 非调试状态下:只存储上下cookie和数据本身大小。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jifqo83R-1692757106962)(image/动态分配)]

6.1动态分配下的数组array存储

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-21x9yotD-1692757106963)(image/array)]

对于内有3个元素的数组存储,调试模式下(左1)内存分配包括3个数组元素+上下Debugger Header+上下cookie+表数量(83+(32+4)+42+4),以及不满16倍内存,自动增加内存直至为16倍数;左二是非调试模式

6.2dete []和delete的区别

delete的动作分为两步,首先调用析构函数删除变量,然后删除其内存。所以如下调用delete的时候,系统会自动调用析构函数,然后根据上下cookie给出的内存大小进行整块内存删除。因此对于数组内存储的是非指针变量的array new,delete不加[],是不会造成内存泄漏的。但是!!!如果内部存储的是指针,那么没有加[],编译器会不知道需要需要调用多次析构函数,从而造成只删除第一个数组元素及其指向的数据,而不会删除后续的。内存泄漏的部分是剩余数组内指针元素指向的部分。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DvGsujTg-1692757106963)(image/delete【】)]

7.深拷贝和浅拷贝

深拷贝:指拷贝对象的具体内容,二内存地址是自主分配的,拷贝结束之后俩个对象虽然存的值是一样的,但是内存地址不一样,俩个对象页互相不影响,互不干涉

浅拷贝:对内存地址的复制,让目标对象指针和源对象指向同一片内存空间。注意:当内存销毁的时候,只想对象的指针,必须重新定义,才能够使用。浅拷贝是一个传址,也就是把a的值赋给b的时候同时也把a的址赋给了b,当b(a)的值改变的时候,a(b)的值同时也会改变;一般情况下在设计类的时候,调用编译器默认的拷贝函数导致的出现的浅拷贝


8.static说明

c++的静态变量和静态函数是一种特殊类型的成员,具有不同于普通成员的特殊性质,静态变量和静态函数都是与类相关的,而不是与类的实例相关的。

8.1 静态变量

  1. 在C++中,静态变量是在类的定义中声明的变量,它们不属于任何特定的类实例,而是属于整个类。静态变量只有一个副本,并且可以在任何实例中使用,不需要创建实例。静态变量的作用是存储与类相关的数据,这些数据不需要每次创建新实例时都重新分配内存。因此,静态变量可以用于实现全局共享状态。

  2. 带有this pointer;

  3. 使用场景:对于不同类名的某一属性,只能有一份,可以说是类属性而不是实例属性

  4. 静态成员数据在类内声明并且一定要在类外进行定义(类外定义时不需要加static标识符),赋不赋初值都是可以的,注意写法。

  5. **为什么静态成员不能在类内初始化?**因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的。但如果是静态常量成员就会就会在类内初始化

    class account
    {
        public:
        static double m_rate;//静态变量
        static void set_rate(const double & x){m_rate = x;}
    };
    double account :: m_rate=8.0;//类外定义
    
class Counter {
public:
    Counter() {
        count++;
    }

    static int getCount() {
        return count;
    }

private:
    static int count;
};

int Counter::count = 0;//类外定义

int main() {
    Counter c1;
    Counter c2;
    Counter c3;
    std::cout << "Number of instances created: " << Counter::getCount() << std::endl;
    return 0;
}
//静态变量count用于计算Counter类的所有实例的总数。每当创建Counter的新实例时,构造函数会将count的值增加1,我们创建了三个Counter实例,然后调用Counter::getCount()函数,该函数返回了创建的Counter实例的总数。

8.2 静态函数

  1. 在C++中,静态函数是与类相关联的函数,它们不依赖于类的任何实例。静态函数可以像全局函数一样访问静态变量和其他静态函数,并且它们不能访问非静态变量和非静态函数。静态函数的作用是实现与类相关的功能,而不需要创建实例。这使得静态函数在实现单例模式等场景时非常有用。
  2. 因为静态成员函数属于类,不属于对象,所以没有this pointer,只能存取,处理静态数据

调用静态函数的方式有两种:

  • 通过对象调用

  • 通过class name调用


9.把构造函数放在private(Singleton,利用静态实现该种设计模式)

class A
{
public:
    static A& getInstance() {return a; };
    setup(){......}
private:
    A();
    A(const A& rhs);//放在private,不想外界创建对象
    static A a;//仅一份的创建,就算不调用静态函数时,对象也已经被创建了
...
};

A::getInstance().setup();//通过这种方式来调用唯一的类的函数

针对于上面说明,我不想外面能创建这个类的对象就把构造函数放在private块,那只能这个类的“内部”的函数才能构造这个类的对象了。缺陷:如果外界不需要A,那么已经被创建的a就会造成内存占用浪费


//设计模式singleton模式class A大致写法
class A
{
public:
    static A& getInstance()//只有调用这个函数,对象才会被创建,离开过后其唯一的对象也会保留,在类外定义不能写在main()里面
    { static A a;
        return a;
    }
    setup(){cout<<"b"<<endl;}
private:
    A(){cout<<"a"<<endl;};
    A(const A& rhs);
};


A::getInstance().setup();//通过这种方式来调用唯一的类的函数

还有一个情况是:通常将拷贝构造函数和operator=(赋值操作符重载)声明成private,但是没有实现体。这个的目的是禁止一个类的外部用户对这个类的对象进行复制动作。

10.类模板和函数模板

函数模板

template <class T>
inline
const T& smin(const T& a, const T& b)
{
    return b<a? b:a;
}


class stone
{
public:
    stone();
    bool operator < (const stone& rhs) const
    { return  _weight < rhs._weight; }

private:
    int _w, _h, _weight;
};

//使用
stone r1(2,3), r2(2,3), r3;
r3 = smin(r1, r2);

10.1函数模板和类模板的区别

  1. 语法格式都差不多,第一行是template开头,后面跟着如果是函数,就叫函数模板,如果跟着是类,就叫类模板。

  2. 类模板不支持参数自动推导:

    • 函数模板中参数,支持参数类型自动推导,在类模板中就不行,必须要明确给出参数类型才可以,否则报错。

      //类模板测试
      template<class TypeName,class TypeAge>
      class Person
      {
      public:
      	Person(Typename name , TypeAge age)
      	{
      		this->m_name = name;
      		this->m_age = age;
      	}
      	void showInfo()
      	{
      		cout << "Name: " << m_Name << " Age: " << m_Age << endl;
      	}
      
      private :
      	TypeName m_name;
      	TypeAge m_age;
      };
      
      //使用
      
      int main()
      {
      	Person<string, int>p1("zhangsam", 10);  //正确
      	Person p2("lisi", 12);					//报错(未指定模板参数类型)
      }
      
      //函数模板测试
      template<class typename1,class typeage>
      void get_name_age(typename1 name,typeage age)
      {
      	cout << name << " " << age << endl;
      }
      int main()
      {
          get_name_age("zhangsan",10);//根据实参自动推导类型
      }
      
      
  3. 类模板在模板参数列表可以有默认参数

template <class TypeName, class TypeAge = int>
    //设置了默认,如果模板参数不写,自然就是采用默认的int类型,如果模板参数传入了,就使用模板参数中传入的类型。

11.namespace

11.1什么叫做命名空间

您的C++应用程序中也会出现同样的情况。例如,您可能正在编写一些具有名为xyz()函数的代码,并且还有另一个可用的库,它也具有相同的xyz()函数。现在编译器无法知道您在代码中引用的xyz()函数的哪个版本。

名称空间(namespace)被设计来克服这个困难,并被用作额外的信息来区分类似的函数、类、变量等等,它们在不同的库中具有相同的名称。

  • 命名空间只能全局范围内定义

  • 命名空间可以嵌套

    namespace Name
    {
    	namespace Internal
    	{
    		/*...*/
    	}
    	/*...*/
    }
    
  • 命名空间是开放的,可以随时把新的成员加入进来

  • 命名空间可以存放变量和函数

  • 命名空间中的函数可以命名空间外定义(记得加作用域)

  • 命名空间的使用

    使用整个命名空间:using namespace name;
    使用命名空间中的变量:using name::variable
    使用默认命名空间中的变量:  ::variable
    

三种写法

using namespace std;//一次把标准库全打开。一些常见小程序用这种
{
cout<<...
}

//为了防止标准库全打开,会出现混乱,那么单独打开需要的内容
using std::cout;//说明哪一个就不用全名了
{
std::cin>>...//没有说明就用全名
cout<<..
}

//
{
std::cout<<...
std::cin>>...
}









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值