C++ 学习笔记初阶

ps:终于来到c嘎嘎了,芜湖!!!

如何学:思考+画图

C++入门

在最开始之前,老规矩,看一下c++的hello world输出。

C++ 学习笔记初阶_第1张图片

ok,接下来会一点点带入c++。

c++关键字

c++的关键字差不多有60个左右,c++只不过是c的扩充,所以基本关键字学过c的大家都知道就不赘述了,至于新增加的,见到一个解释一个。

命名空间

在实际应用场景下,一个项目都是好多人做的,最后把每个人的代码汇总到一起,这其中就避免不了起名的冲突,预防这点,c++之父本贾尼引入了一个新的定义“namespace”意味使用后接的内容(前提是包含这个文件),他的使用有个前提,需要所引用文件代码对防止冲突函数名等进行“打包”

namespace jwp
{
    int a = 1;
    int add(int x , int y)
    {
        return x + y;
    }
}

以上为打包过程,在主文件引用次文件后,需要对其“解开”才行。

有时候只需要“解开”单个函数或者仅一次使用,也可以以下操作。

std::cout << "hellow world" << std::endl; 
using std::cout

第一行就是仅仅一次使用,第二个就是对该包装(std)内的cout多次使用不需要加std::。

至于using意思就是全解开,全使用的意思,比如刚刚的cout,意思就是对std中的cout全放开。

ps:真正使用肯定会有很多问题,按照常理思维想一下应该问题不大。

c++输入&输出

输出大家已经看到了,就是cout和<<,意思也不难,就是<<后的符号要“流进”cout中并且打印出来。相而,输入就是cin>>[a],意思就是数据“流入”[a]。

在上面的hello world中,endl意思就是换行,这个指令“流入”前面的字符,意思合起来就是这段字符要输出并且打印。

ps:c++和c一句话就是本是同根生,怎么方便怎么来,混起来不犯毛病。

缺省参数

直接来

void add(int a = 10, int b = 20, int c = 30)
{
    printf("%d %d %d", a, b, c);
}
int main()
{
    add(1);
    return 0;
}

当传参时,引用的是传参值,无参时,用的是函数中的自带值。

注意:传参时不能跳着传,只能从左往右传(假设上面那个就是a=1,b=20,c=30);只能在声明给,不能声明和定义同时给。

(编程中的天狗,有伴了就不需要,没人了就被需要了,dddd)

然后上面那个叫全缺省,还有一种叫半缺省,意思就是参数a可以不给数值,只给b和c。

函数重载

简单来说就是在同一作用于下允许函数重名,然后根据不同的参数类型去选择使用哪个函数;还有就是,他只会辨别参数类型,不会辨别返回值。

补充一下:除了 参数函数类型 还有参数个数,参数类型顺序。

而编译是怎么区分两个名字一样的函数的呢。这就展示出了c和c++的区别(本来就是同一种,真的不想去拉踩,就像手心手背一样)c++在编译链接时会在修饰函数时多加上刚才说的三种。

int add(int a, int b)
{
    return a + b; 
}
//_Zaddii(函数地址)
int add(char a, char b)
{
    return a + b;
}
//_Zaddcc(函数地址)

相信聪明的大伙看出来了区别和规则,这,就是Linux的函数修饰。当然不同平台规则不一样。而c呢,就单纯的在调用时用函数名查找了,没有修饰的那种。

这里说完修饰就能解释为什么不会辨别返回值了。

引用

就是取个别名,有个外号,写一段代码,简单说一下。

int i = 1;

int j = i;

int& k = i;

int& n = k;

int&就是“取别名”,他和直接等于的区别有两点,第一点等于的话地址会不同,&后地址相同;第二点,别名改变会影响本体,别名的别名改变也会影响本体,依次类推,但等于不会改变。

好,那他的实际应用场景是什么呢,最直接的就是形参和实参。


int add(int& x, int& y)
{
    int tmp = x;
    int x = y;
    int y = tmp;
}

int main()
{
    int i = 1;
    int j = 2;
}

这样, x 和 y 就不是 i 和 j 的一份临时拷贝了,而是 i 和 j 的别名,这样,就很完美的解决了指针的冗杂使用。

(这里说的并不全面,函数引用以及传值,指针,引用的内存及时间后续更新)

{

        这里说一个小知识点:

        int a = 1;

        double& b = 1;

        这是运行不了的,问题并不是说int转double转不了,而是一个权限放大的问题,权限只能缩小或者平移,不能放大,而对应的这段代码在哪里放大了呢,新的知识就来了。

        在看不到的地方,系统默认创建了一个临时变量double来接收 a 的值,这个临时变量时有const修饰的,然后再把这个临时变量的值给 b ,一切也就说的通了,至于为什么这么做,以后再说。

}

内联函数

这个是用于替代宏的一个函数,一般用const和enum替代宏常量,inline替代宏函数。

因为宏确实有很多缺点,

第一不能调试,在编译的时候就替换掉了。 

第二没有类型安全的检查。

第三在某些场景非常复杂。

比如以下这个ADD宏函数

#define ADD(int x, int y) return x + y;

#define ADD(x, y) x + y;

#define ADD(x, y) (x + y);

#define ADD(x, y) (x) + (y);

#define ADD(x, y) ((x) + (y));

对于不熟练的这里的可能就写出这样的东西来,恰恰就说明的宏函数的复杂。

再比如说,假设某个函数有十行,被调用了10000次,这里的代码在替换的时候,咦,可执行程序太大了。

所以他们也仅仅只适用于一小段代码。

这里要注意的点是,内联函数并不会像一般函数一样,有类似于压栈,展开之类的底层操作,只会单纯的找到对应链接,即在使用的时候,声明和定义不可以分离。直接在声明里敲出来就好。

因为博客写到了类和对象这个章节,这里有句话可以讲了,就是成员函数要是写在类里,会被认成内联函数。

auto关键字

最简单的说法就是自动识别变量类型并给予,就是当类型用就行,而对应的就产生了一个检查类型的函数,如下

int a = 0;

auto b = a;

auto c = &a;

cout << typeid(b).name() << endl;

cout << typeid(c).name() << endl;

可能我这样举例显得这个auto很鸡肋,实则不然,在迭代器或是其他的很长很长的类型名的时候,用这个就很爽。要说缺点吧 就像双刃剑一样,是十分便利,但是恰巧,当你不知道这个类型的时候,只能去查,就看个人熟练度呗。

有一点要注意的是auto不能做形参,也不能做数组。

基于范围的for循环

这个真的是太爽了,真的好便利。

正常我们在遍历数组的时候 是这样(数组a[])

for(int i = 0 ; i < sizeof(a)/sizeof(int) ; i++)

{

        cout << a[i] << " ";

}
在这里有一个范围for写出来就是这样、

for(auto e : a)

{

        cout << e << " ";

}

意思就是依次取数组中的数组赋值给e对象, 自动判断结束。

这里的auto 和 e 不是必须的

那么假设我想改数组里的值我该怎么做呢 如下

for(auto& c : a)

{

        c*= 2;        

        cout << e << " ";

}

还是之前的知识,引用和auto哦

指针空值—nullptr

在语言的长河里,难免会有那么一些坑,比如NULL。在c++某些场景里就被定义成了0,所以就有了nullptr来替代null

语言的发展里有一个原则,就算知道是坑,也不能改,只能向前兼容,遇到坑了,只能打补丁,因为这是语法,很多人在用,只要改了,可能别人的就挂了。

类&对象

终于码完这些边边角角了,虽然不是很全,但能进入这个章节了。

简单说就是把结构体加深改造了,让他更安全了等等。

那什么是类和对象,简单说就是房子蓝图和房子。这里c++自定义了一个新的变量名字class。

class jwp

{

        public:

                void test1();

                int _d;

        private:

                int _a;

                int _b;

                int _c;

        protected:

                void test3():

};

以上呢,就是class的用法,这个呢,就是蓝图,首先定义了一个变量名,然后分别设置了里面内容的使用权限:

public:公有访问,谁都可以看,谁都能用,一般只放函数,不会存在对象里

private:私有访问,只有结构体自己可以用,其他人不行,能很好的保护,一般放数据

protected:保护访问,同上,结构体自己和自己派生出来的可以访问

那怎么创建房子呢,如下:

int main()

{

        jwp p1;

        p1.test1();

        p1._d = 3;

        return 0;

}

用结构体的变量名创建一个对象,然后像struct一样去用就可以了。

这里的变量 a b c d 我在他们的前面都加了一个“_”,可能会显得多此一举,其实不是的,想象以下一种情景

class jwp

{

        public:

                void test1(int a, int b)

                {         

                 a = a;

                 b = b;

                }       

        

      private:

                int _a;

                int _b;

       

};

其实这里不免看不来,这几个变量给的就很迷惑,虽然说意思也能大致明白,就是把函数里的变量再存回结构体,就是很难受看着,加个下划线就会好很多。

接下来再讨论下成员变量和成员函数的位置问题:

C++ 学习笔记初阶_第2张图片

 C++ 学习笔记初阶_第3张图片

 这里我们简单的看下对于他们的大小做了下计算,结果告诉我们,里面只有两个整形变量的大小“8”。并没有成员函数,其实这里转换到现实就可以很简单理解,首先数据是每个人都有自己单独的那一份,而函数是每个人都用一套,就像在一个小区里,每个人都有着属于自己独特的房间(数据),但是像公共健身器材啊,小卖部啊等等都是公用的。

那他们的存储位置呢就是成员变量存储在对象中,成员函数存储在公共代码段。

这里有一个比较有意思的问题:

C++ 学习笔记初阶_第4张图片

试问这里的d1有多大,是零呢,还是什么,我们来运行下。

 C++ 学习笔记初阶_第5张图片

结果是一,怎么理解呢,还是转换到现实,虽然我这个图纸是空的,什么都没有,但是我有施工场地,我有这个实实在在的1,那些已经装修完的可能很大或者怎样,我这就是1.

内存里也是这样,类是空的不代表以后有没有,所以就拿1占个位置。

 下面就说一下同一个class多个class变量的问题,就是说都用同一个函数存了很多的变量,如以下情况,是怎么做到各存各的呢。

C++ 学习笔记初阶_第6张图片

 这里其实是有隐形参数的如下

class jwp
{
    public:
        void data(Data* this,int year,int  day)
        {
            this->_year = year;
            this->_day = day;
        }
private:
    int _year;
    int _day;
};
int main()
{
    jwp d1;
    jwp d2;
    d1.data(&d1,1, 2);
    d2.data(&d2,1, 2);
    return 0;
}

像上面的this啊,&d1啊,都被隐藏了,而d1什么的就是区分不同对象的方法。

C++ 学习笔记初阶_第7张图片

 这里是验证

然后就是这个隐形参数this的存储位置,无论怎么说,他是个参数,理论就是存储在栈里。

默认成员函数:

这里开始进入重点,六个默认成员函数:

1.构造函数(初始化)析构函数(清理)

2.拷贝构造(初始化一个一摸一样的)复值重载(把值复值给另一个)

3.取地址重载 const成员函数 (第四和五都属于运算符重载,六是一个权限函数)

默认成员函数才是类和对象的重点,以上是可以系统自动生成的,也可以是自己写,自己写了系统就不会自动生成了,都叫默认成员函数,有 且只有一个。我会以我认为最简单的方式给大家代入这里,步骤为:首先自己实现,然后说下需要注意的细节。

构造函数:

我们先看这么一段代码

#include 
 
class Date {
public:
    void SetDate(int year, int month, int day) 
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print() 
    {
        printf("%d %d %d\n", _year, _month, _day);
    }
 
private:
    int _year;
    int _month;
    int _day;
};
 
int main(void)
{
    Date d1;
    d1.SetDate(2022, 3, 8);
    d1.Print();
 
    Date d2;
    d2.SetDate(2022, 3, 12);
    d2.Print();
 
    return 0;
}

一段关于日期的类,每次都先要 Date 然后 SerDste 就很烦,初始化 传参 初始化 传参,既然这样,我们为何不把他简化或者省略呢,我们接着看下面这段代码

#include 
 
class Date {
public:
    //无参构造函数 
    Date() 
    {
        _year = 0;
        _month = 1;
        _day = 1;
    }
    //带参构造函数 
    Date(int year, int month, int day) 
    {
        _year = year;
        _month = month;
        _day = day;
    }
    //缺省构造函数
    Date(int year = 1, int month = 0, int day = 0) 
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print() 
    {
        printf("%d %d %d\n", _year, _month, _day);
    }
 
private:
    int _year;
    int _month;
    int _day;
};
 
int main(void)
{
    Date d1;   
    d1.Print();
 
    Date d2(2022, 3, 9);   
    d2.Print();
 
    Date d3;

    //Date d4(); 代入缺省看

    return 0;
}

这里有三种,分别是无参,有参,缺省 用法也是如上,需要注意三点

一:可以Date d1 不可以d1.Date() 【构造函数是特殊的函数,不是常规的成员函数】

二:无参构造函数创建对象,对象后面不用跟括号,否则就成了函数声明。

三:带参构造函数,需要传递三个参数。

四:无参和全缺省可以同时存在,但会有二义性从而崩溃(有且只有一个)

这些什么的我觉得太麻烦了这么记,一句话,传参就带括号传全了,不然就不带括号

#include 
 
class Date {
public:
    void Print() 
    {
        printf("%d %d %d\n", _year, _month, _day);
    }
 
private:
    int _year;
    int _month;
    int _day;
};
 
int main(void)
{
    Date d1;  
    d1.Print();
 
    return 0;
}

这个是使用系统自带的构造函数,可是运行结果如下

C++ 学习笔记初阶_第8张图片

是很不理解的随机值,也不铺垫了,这里需要注意的就是系统自带的只能初始化自己认识的,就是

基本类型(内置类型(int char...))不认识自定义类型(class struct...)。

所以呢 大家在使用的时候 酌情处理就好啦

析构函数:

清理就是清理啦,只不过清理的是资源,不是彻底销毁,彻底销毁还是编译器。

1.没有参数和返回值(没有重载概念)

2.和构造一样 有且只有一个

3.生命周期结束后自动调用

4.类中声明时 函数名前面有~

既然涉及到了资源清理 Data 就合适了,我们可以看下面这段开空间的

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		if (_a == nullptr)
		{
			cout << "malloc fail" << endl;
			exit(-1);
		}
		_top = 0;
		_capacity = capacity;
	}

	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	int* _a;
	size_t _top;
	size_t _capacity;
};
int main()
{
	Stack s1;
    Stack s2;
	return 0;
}

这里要注意的就是析构函数只会对自定义类型做处理,内置类型不处理。这样其实挺稳妥的,需要注意的就是涉及到指针这种内置类型开空间的,自己写一个析构就好了。

拷贝构造函数

建立一个对象的同时给他初始化成某一个函数,或者说就是以某个对象为样板进行初始化。

#include 
 
class Date 
{
public:
    Date(int year = 0, int month = 0, int day = 1) 
    {
        _year = year;
        _month = month;
        _day = day;
    }
    //拷贝构造函数
    Date(const Date& d) 
    {      
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
 
private:
    int _year;
    int _month;
    int _day;
};
 
int main(void)
{
    Date d1(2023, 4, 30);
    Date d2(d1); 
 
    return 0;
}

1.他也是初始化,所以他只是构造函数的一种重载形式,没有返回值

2.参数一般用const修饰 因为传过去的对象不能被改变(应该好理解吧?)不加const属于是放大权限了 第二个就是可以防止赋值那里写反了

3.参数必须是引用传参 传值传参会无限循环(这里我不能讲 因为我也理解的不是很透彻 这里原因就是 调用拷贝构造,需要先传参数,传值传参又是一个拷贝构造 无限循环

这里我会以后理解了再重新组织语言

哈哈哈明白了 我知道这句话差什么了

就是说自定义类型在传值传参的时候不能直接传过去,我想半天这一切不是正好吗?咋就循环了

问题就出在这里,他需要先复制一份,把复制的传过去,这里就涉及到了下面说的那个深/浅拷贝,编译器也不管,他也没法管,就统统调用拷贝构造帮他复制一份,注意!!就在这里,复制一份就需要调用拷贝构造,调拷贝构造就需要传参,参是自定义类型,就需要再拷贝构造一份,拷贝构造一份就需要传参,自定义传参就需要拷贝构造一次循环。引用是理想的传值,所以我觉得逻辑可以这么说(理解几天在写一句简单的):

自定义类型传值传参需要额外开数据进行拷贝构造再传(不是直接传),自写拷贝构造的参数在传值时就会 传值 是自定义?是 调用自写拷贝构造  传值 是自定义?是 调用自写拷贝构造 传值 是自定义?是 调用自写拷贝构造...

最后到了注意细节的这里 还是内置类型和自定义类型 ? 不是不是哈哈哈 这里涉及到的是浅拷贝和深拷贝 当前说这个不是很适合 简单说就是 浅拷贝是两个指针指向同一块空间(原本只有一个指针指向一块空间 浅浅的拷贝一下 再让一个指针指向这块空间 这样第二个指针也是这个值了,弊端是容易被析构两次) 深拷贝就是把空间里的内容 再开一块空间放进去 这样就是单独空间单独指针。

现在了解就是拷贝构造基本不用自己写,系统的会适应大部分情况,除了栈。

还有一个就是这里的顺序问题,其他博主写的很学术的太多了,我这里永远只给出我自己的浅显见解:构造是按顺序来的,析构是和他相反的,有class外的先构造,最后析构,静态的顺序构造,比class先析构。

复制重载,取地址重载:

我觉得这俩属于一个大类的一个分支,所以另起一篇:(27条消息) 运算符重载_晚风微凉稍稍寒的博客-CSDN博客

const成员函数

C++ 学习笔记初阶_第9张图片

 这里的aa会报错,原因是不兼容。就是因为在 A aa 前面加了const ,理解起来也简单,就是参数是const ,接收的时候this不是const,那this是隐性参数啊,他怎么加const呢。如下

C++ 学习笔记初阶_第10张图片

 具体使用呢 主要应用于函数

C++ 学习笔记初阶_第11张图片

初始化列表

public里可以声明成员变量,main里可以定义对象,那么怎么定义对象里的单个变量呢,大家可能会想到直接在声明后面给个值就行了。可是那个叫缺省呢。于是我们有了初始化列表,可以定义每个变量。

class A

{

public:

        A()

        :_x(1)

        , _a2(1)

        {

                _a1++;

                _a2--;

        }

private:

        int _a1 = 1;

        int _a2 = 1;

        const int _x; 

};

int main()

{

        A aa;

        return 0;

}

用法呢就是如代码所示,大括号里就是做一些相应操作,冒号开始,逗号结束,然后变量名后面的括号里是初始化值。需要注意的事呢,就是如果私有里的声明有缺省值,但还是以定义为主。只能定义一次。

还有一个就是这种情况:

class A
{
public:
  A(int a)
    :_a1(a)
    ,_a2(_a1)
  {}
  void Print()
  {
    cout<<_a1<<" "<<_a2<

如果是按照前面说的呢,输出的是1 1 ,但实际呢,是1 随机值,因为a2比a1先声明,也就比a1先定义,那个时候a1是随机值,所以就是咯。

需要初始化的变量有三种:引用成员变量,const成员变量,没有默认构造函数的自定义成员变量。

explicit

在两个类型不同的变量之间赋值的时候,比如把一个double给一个int,会发生一个叫隐式类型转化的事情,就是先自动创建一个const临时变量,然后把double转成int给这个临时变量,在把这个临时变量赋值到int变量上。

前景了解过了呢,这个指令就是禁止这个行为的,用法如下:

  explicit A(int a)
    :_a1(a)
    ,_a2(_a1)
  {}

注意的就是只能适用单参数构造参数。

静态成员函数:

class A
{
public:

        A(int a = 0)

        {

                ++count;

        }

        static int Getcount()   //静态成员函数,没有this指针

        {

                //_a++;报错 没有this指针 无法直接访问非静态成员 

                return count;

        }
private:

        static int cout;        //静态成员变量 属于所有对象 整个类 和正常static一样理解

        int _a = 0;

};

int A::count = 0;                //初始化

int main()
{

        A aa[10]; // count是10

        

        cout << A::Getcount() << endl;  //没有this指针,可以直接调用
}

这里我就是想写这么一种如图所示的讲解,就算有什么需要注意的地方我也不知道了。

匿名对象:

就是在创建对象的时候不给名字,直接类名和括号。声明周期只在那一行,一般用于即用即毁的东西。

A();  //比如class一个A 然后创建一个A的匿名对象

友元函数:

一般写在public里的一个函数,然后这个函数可以访问private里的值。用法还是,在函数重载用过

1.可以访问私有和保护,但不是成员函数

2.不能用const修饰

3.可以在类的任意地方声明,不受类访问限制符限制

4.一个函数可以是多个类的友元函数

5.和普通函数调用原理相同

6.被友元函数不是友元函数的友元

内部类:

就是在类的里面再定义一个类。

class A

{

public:

        class B

        {

                public:

                                int _a;

        };

private:

        int _b;

};

这里说下大小就明白了,sizeof一算还是4,说明A和B是独立的,只不过这个B受类域限制罢了。

这里B最大的优势就是创造出来就是A的友元。

最后说点细节:

部分编译器自动优化:

当A=B=C时,理论上是先调用构造函数,再拷贝构造函数这么个顺序,但是实际上呢,编译器帮我们自动优化成了直接构造,但是在函数传值和传引用就不会优化(前面说的是直接传对象会优化)。所以

对象总结下就是:

1.接收返回值对象,尽量拷贝构造接收,不赋值接收

2.函数有返回对象时,尽量返回匿名对象

函数传参总结:

尽量使用const和 & 传参

wdm 这么多天 终于把这里写完了 2023.5.9 17点14分 芜湖

c++内存管理

说是内存管理,其实就是这两个操作符(动态内存申请和释放),在C语言阶段,我们开空间用的是malloc和与之对应释放空间的free。c++新引进了一套:new和delete。用法:

int* p1 = new int;                            //开辟一个int 不会初始化

int* p2 = new int(0);                       //开辟一个int 初始化成0

int* p3 = new int[10];                     //开辟十个int 不会初始化

int* p4 = new int[10]{1,2,3,4};       //开辟十个int 前四个按照顺序赋值,剩余初始化成0       

delete p1;

delete[] p3;

以上就是new和delete的简单用法,需要特别注意的呢,就是二者不要交叉使用,因为delete可能会调用一些c++专有的像析构函数之类的,交叉就会引发一些 @..%xx$**^! 类似与这样的问题。

检查malloc是否错误需要判空,new错误会直接抛异常。

---------------------------------------------------------

有上面的理解就够了,以下说一些算是加深的把

上面说过这两个是操作符,为什么不是函数呢,operator new和operator delete 是系统自带的两个全局函数,在对应的操作符调动下与之运行。然而 operator new 呢,又是malloc的封装函数

(这句话可以理解成:如果没有自定义的operator new可以使用,就调用全局的,就是系统自带的,然后这个自带的就会调用malloc,再调用构造函数(总的来说operator new就是比malloc多了个构造))

stack st

stack* pst = new stack

这两个都是开空间嘛,区别呢,就是st是在栈上取名,在堆上开空间,pst呢,在栈上取名,new在堆上给他malloc一个空间,然后这块空间又在堆上构造一个空间,最后用的构造完的这块空间。

图示:

C++ 学习笔记初阶_第12张图片

所以pst被清理时会先调用析构函数,再使用operator delete清理,相而free会直接清理第一次在堆上开辟的空间从而导致内存泄漏(c/c++以效率为优先,所以内存类的东西都是程序员自己搞定,要自己负责滴)。

第二个是开辟多个空间但直接delete的问题:

int* p1 = new int[10];

delete p1; 

会直接报错,因为开十个空间的时候做了一个小动作,在整个数组前再开四个字节保存这块空间的大小,然后在正常使用delete时,会先减一,读取这个数字,就可以完美的delete了。

C++ 学习笔记初阶_第13张图片

 但是用free就可能不会报错,因为多开一个空间只是为了知道要析构的次数,当不使用析构时,就不会多开一个空间,就会正常运行。

---------------------------------------------------------

模板

就是说一个函数因为功能一样但参数不一样,就根据这么一种使用状况,有了这么个东西。

template

void swap(T& x, T& y)

{

        T tmp = x;

        x = y;

        y = tmp;

}

int mian()

{

        swap(int a=1, int b=2);

        swap(double a=1.1, double b=2.2);

        return 0;

}

这就是个模板函数,temptlate就是他的关键字 用class也行,用typename也行,至于T就更随便了。

其实这里用的并不是同一个函数,表面上是一个,就像Linux的两个返回值问题一样,是两个。

调用的这个过程,这个具体创造函数的过程叫实例化。看着代码是减少了,实际还是那样。

这里传过去的参数类型必须是一致的,如果不一样可以强转。

还有一个就是显示实例化 

add(a,b);

这样就默认了两个参数都被强转成了int。

当模板和实例化后的函数一同出现的时候,编译器会考虑性能优先调用实例化后的(匹配的情况下)如果两者同时存在还只想用实例化后的就用显示实例化那套。

template

typedef n 10

class A

{

public:
        T _a[n];        

}

int mian()

{

        a a1;

        a a2;

        return 0;

}

在这个时候,确实是用模板可以完美的创建出两个不同类型对象,但是假如我想让a1等于10,a2等于20,该怎么做,好像一下就尬住了。(可能有些小聪明说直接赋值,那我要是再在类里定义一个量呢,这可是类啊),所以这个时候就有了新的玩法。

template

class A

{

public:
        T _a[n];        

}

int mian()

{

        a a1;

        a a2;

        return 0;

}

如上所示,这一类呢,叫做非类型模板参数(必须是整型),也是可以给缺省值的。

模板特化:

对于某一个特殊的,想对其类型特殊化处理,其实看着就是重载了一下费了个事,但是在某些场景有奇效,

template

bool less(T left, T right) 

{

        return left

}

//特化版本

template<>

bool less(Date* left, Date* right)

{

       return *left < *right;    
}

int mian()

{

        cout << less(1,2) << endl;

       

        Date d1 (2022,7,7);

        Date d2 (2022,7,8);

        cout << less(d1,d2) << endl;        

         return 0;

}

上面这个特化版本属于是全特化,下面这个是偏特化,对全特化的范围缩小了

template

struct less

{

        bool operator()(const T* l, const T* r) const

        {

                return *l < *r;

        }
}

模板的分离编译:
简单点说就是声明和定义不在同一个文件下,调用时找不到链接,这个时候只需要显示实例化就可以了。

template

int add(const int& left , const int& right);

但是都用模板了,类型就肯定不是一个了,用一个类型实例化一个,就很麻烦,不用这么麻烦也行,声明和定义在一起就可以了。

ps:模板问题一般都是在开头。

那么c++初阶就告一段落啦,其实三月末就该写完的QAQ。

你可能感兴趣的:(c++,开发语言)