C++基础(三):C++入门(二)

news2024/11/23 7:46:57

        上一篇博客我们正式进入C++的学习,这一篇博客我们继续学习C++入门的基础内容,一定要学好入门阶段的内容,这是后续学习C++的基础,方便我们后续更加容易的理解C++。

目录

一、内联函数

1.0 产生的原因

1.1 概念

1.2 特性

1.3 面试题

二、缺省参数

2.1 缺省参数的概念

2.2 缺省参数的分类

2.2.1 全缺省参数

2.2.2 半缺省参数

2.3 多文件结构的缺省参数函数

2.3.1 缺省参数补充

三、函数重载(重点)

3.0 函数重载的引入

3.1 函数重载概念

3.2 判断函数重载的规则(编译器的工作)

3.3 函数重载解析的步骤

3.4 函数重载判断练习

3.5 C++支持函数重载的原理-名字修饰(name Mangling)

3.5.1 预备知识

3.5.2   C语言编译时函数名修饰约定规则(Windows平台下)

3.5.2   C语言编译时函数名修饰约定规则(Linux平台下)

3.5.3  C++编译时函数名修饰约定规则(Windows平台下)

3.5.3  C++编译时函数名修饰约定规则(Linux平台下)

3.6 C++函数重载的名字粉碎(名字修饰)

3.7 如何指定函数以C方式修饰函数名还是以C++方式修饰函数名

四、函数模板(重点)

4.0 产生的原因   

4.1 函数模板定义

4.2 数组的推演及引用的推演

4.3 利用函数模板实现泛型编程

4.4 模板函数的重载与特化(完全特化、部分特化、泛化)

4.5 类模板

五、名字空间:namespace

5.1 C++作用域的划分

5.2  命名空间

5.3 命名空间的定义

5.4 命名空间使用

5.4.1 加命名空间名称及作用域限定符

5.4.2 使用using将命名空间中某个成员引入

5.4.3 使用using namespace 命名空间名称引入


一、内联函数

1.0 产生的原因

        当程序执行函数调用时,系统要建立栈空间,保护现场,传递参数以及控制程序执行的转移等等, 这些工作需要系统时间和空间的开销。当函数功能简单,使用频率很高,为了提高效率,直接将函数的代码嵌入到程序中。但这个办法有缺点,一是相同代码重复书写,二是程序可读性往往没有使用函数的好。因此,便产生了内联函数,它的作用主要如下:

  • 1、减少函数调用开销:函数调用通常会有一些额外的开销,包括压栈、跳转、返回等。这些开销对于频繁调用的小函数(如访问器函数、简单的计算函数)而言,可能会显得过高。通过将这些小函数定义为内联函数,可以避免这些开销,因为内联函数会在编译时直接将函数代码插入到调用点。

  • 2、增强代码可读性:通过内联函数,可以将一些频繁使用的代码块抽象为函数,从而提高代码的可读性和可维护性。同时,因为内联函数在编译时会被展开,所以在运行时不会引入函数调用的开销。

#include<ctype.h>  //C语言中的字符处理函数库
#include<iostream>
using namespace std;

boo1 IsNumber(char ch)
{
    return ch >= 'O' && ch <= '9' ? 1 : 0;
    //return isdigit(ch) ;
}

int main()
{
    char ch;
    while (cin.get(ch), ch != '\n')
    {
        if (IsNumber(ch))
        {
            cout << " 是数字字符" <<endl;
        }
        else
        {
            cout << "不是数字字符 " <<endl;
        }
    }
    return 0;
}

如果上述代码判断数字字符函数在程序频繁调用,那么将会产生一笔不小的空间开销和时间开销。为了协调好效率和可读性之间的矛盾,C++提供 了另一种方法,即定义内联函数。

1.1 概念

       以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开(在编译期间编译器会用函数体替换函数的调用。,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。

查看方式:

  1.  在release模式下,查看编译器生成的汇编代码中是否存在call Add
  2. 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不会对代码进行优化,以下给出vs2019的设置方式)

1.2 特性

  1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
  2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建 议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。下图为 《C++prime》第五版关于inline的建议:
  3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。

哪什么情况下采用inline处理合适,什么情况下以普通函数形式处理合适呢?这里有个建议给大家。
         如果函数的执行开销小于开栈清栈开销(函数体较小),使用inline处理效率高。如果函数的执行开销大于开栈清栈开销,使用普通函数方式处理。

1.3 面试题

内联函数与宏定义区别:

  1. 内联函数在编译时展开,带参的宏在预编译时展开。
  2. 内联函数直接嵌入到目标代码中,带参的宏是简单的做文本替换。
  3. 内联函数有类型检测、语法判断等功能,宏只是替换。

二、缺省参数

2.1 缺省参数的概念

         一般情况下,函数调用时的实参个数应与形参相同,但为了更方便地使用函数,C++也允许定义具有缺省参数的函数,这种函数调用时,实参个数可以与形参不相同。缺省参数指在定义函数时为形参指定缺省值(默认值)。这样的函数在调用时,对于缺省参数,可以给出实参值,也可以不给出参数值。如果给出实参,将实参传递给形参进行调用,如果不给出实参,则按缺省值进行调用。

       缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。

2.2 缺省参数的分类

2.2.1 全缺省参数

2.2.2 半缺省参数

注意事项:

      缺省参数可以有多个,但所有缺省参数必须放在参数表的右侧,即先定义所有的非缺省参数,再定义缺省参数(半缺省参数必须从右往左依次来给出,不能间隔着给)。这是因为在函数调用时,参数自左向右逐个匹配(函数调用时同样从左往右给实参,不能间隔着给),当实参和形参个数不一致时只有这样才不会产生二义性。

2.3 多文件结构的缺省参数函数


       函数的原型(声明):由返回值类型+函数名+形参表,形参名称可以省略,但必须有形参类型
指针变量参数为缺省值的时候,指针变量名不可以省略!

      缺省参数不能在函数声明和定义中同时出现,因为如果函数的声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值!

      习惯上,缺省参数在公共头文件包含的函数声明中指定, 不要函数的定义中指定。如果在函数的定义中指定缺省参数值,在公共头文件包含的函数声明中不能再次指定缺省参数
值。

2.3.1 缺省参数补充

         缺省实参不一定必须是常量表达式,可以使用任意表达式。当缺省实参是一个表达式时在函数被调用时该表达式被求值。

 

C语言不支持缺省参数(编译器不支持)

三、函数重载(重点)

       自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。 比如:以前有一个笑话,中国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是“谁也赢不了!”,后者是“谁也赢不了!”

3.0 函数重载的引入

      C语言实现int, double,char类型的比较大小函数。如下:

int    my_max_i(int a, int b) 
{ 
	return a > b ? a : b;
}

double my_max_d(double a, double b)
{ 
	return a > b ? a : b; 
}

char   my_max_c(char a, char b)
{ 
	return a > b ? a : b;
}

         很容易发现它们的共同特点:这些函数都执行了相同的一般性动作; 都返回两个形参中的最大值;从用户的角度来看,只有一种操作,就是判断最大值,至于怎样完成其细节,用户一点也不关心。这种词汇上的复杂性不是”判断参数中的最大值“问题本身固有的,而是反映了程序设计环境的一种局限性:在同一个作用域中出现的函数名字必须指向一个唯实体(函数体)。这种复杂性给程序员带来了一个实际问题,他们必须记住或查找每一个函数名字。函数重载把程序员从这种词汇复杂性中解放出来。

#include<iostream>
using namespace std;

int max(int a, int b) 
{ 
	return a > b ? a : b;
}

double max(double a, double b)
{ 
	return a > b ? a : b; 
}

char max(char a, char b) 
{ 
	return a > b ? a : b; 
}

int main()
{
	cout << max(12, 23) << endl;
	cout << max(12.23, 23.45) << endl;
	cout << max('a', 'b') << endl;

	编译器在编译的时候就已经确定数据类型,从而匹配相应的函数


	return 0;
}

3.1 函数重载概念

       函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型 不同的问题。

#include<iostream>
using namespace std;

// 1、参数类型不同构成函数重载
int Add(int left, int right)
{
    cout << "int Add(int left, int right)" << endl;
    return left + right;
}

double Add(double left, double right)
{
    cout << "double Add(double left, double right)" << endl;
    return left + right;
}

// 2、参数个数不同构成函数重载
void f()
{
   cout << "f()" << endl;
}

void f(int a)
{
   cout << "f(int a)" << endl;
}

// 3、参数类型顺序不同构成函数重载
void f(int a, char b)
{
    cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
   cout << "f(char b, int a)" << endl;
}

int main()
{
   Add(10, 20);
   Add(10.1, 20.2);
   f();
   f(10);
   f(10, 'a');
   f('a', 10);

   编译器在编译的时候就已经确定数据类型,从而匹配相应的函数
   return 0;
}

编译器的工作:
       当一个函数名在同一个域中被声明多次时,编译器按如下步骤解释第二个(以及后续的)的声明。如果两个函数的参数表中参数的个数或类型或顺序不同,则认为这两个函数是重载。而不认为是函数的重复声明(编译器报错!)。

3.2 判断函数重载的规则(编译器的工作)

       这一小节来学习,编译器是如何识别函数重载的,只有我们明白了编译器判断的规则,我们才能正确使用函数重载!

1.如果两个函数的参数表相同,但是返回类型不同, 会被标记为编译错误:函数的重复声明。

int my_max(int a, int b)
{
	return a > b ? a : b;
}

unsigned int my_max(int a, int b) // 编译报错,被认为是函数的重复声明;
{
	return a > b ? a : b;
}

int main()
{
	int ix = my_max(12, 23);
	unsigned int =my_max(12, 23); // 编译无法通过;
	reutrn 0;
}

为什么呢?

         函数的返回类型不能作为函数重载的依据,编译器区分函数是依靠函数声明中的函数名和参数的类型,编译器不知道应该调用哪个函数,编译器认为是同一个函数重复声明;

2.参数表的比较过程与形参名无关。

//编译器会把下面这两个函数认为是同一个函数,编译报错,函数重复声明
int my_add(int a, int b);
int my_add(int x, int y);

为什么呢?

        函数的参数列表中的参数名不能作为函数重载的依据,同样,因为编译器不以参数名称来区分函数,被认为是同一个函数,编译器认为是同一个函数重复声明;

3.如果在两个函数的参数表中,只有缺省实参不同,编译器同样认为是同一个函数重复声明;也就是说它认为这是一个函数。

//编译器会把下面这两个函数认为是同一个函数,编译报错,函数重复声明
void Print(int* br, int n);
void Print(int* br, int len = 10);

4.typedef名为现有的数据类型提供了一个别名,它并没有创建一个新类型,因此,如果两个函数参数表的区别只在于一个使用了typedef重命名,而另一个使用了与typedef相应的类型。则该参数表被视为相同的参数列表,编译器同样认为是同一个函数重复声明;也就是说它认为这是一个函数。

//编译器会把下面这两个函数认为是同一个函数,编译报错,函数重复声明
typedef unsigned int u_int;
int Print(u_int a);
int Print(unsigned int b);

5.当一个形参类型有const或volatile修饰时,如果形参是按值传递方式定义,在识别函数声明是否相同时,并不考虑const和volatile修饰符.(不考虑CV特性)

//编译器会把下面这两个函数认为是同一个函数,编译报错,函数重复声明
void fun(int a);
void fun(const int a);

6.当一个形参类型有const或volatile修饰时,如果形参是按照指针或引用定义时,在识别函数声明是否相同时,就要考虑const和volatile修饰符.(考虑CV特性),那么又该如何考虑呢?

#include<iostream>
using namespace std;

void print(int &a)
{
    cout<<"左值引用"<<endl;
}


void print(const int &b) 
{
    cout<<"常性左值引用(万能引用)"<<endl;
}


void print(int &&b) 
{
    cout<<"右值引用"<<endl;
}


int main()
{
    int x = 10;
    print(x);    //优先匹配左值引用

    const int y = 10;
    print(y);    //只匹配常性左值引用/常引用!(万能引用)

    print(10);   //优先匹配右值引用

    return 0;
}


匹配规则如下:

       首先,C++只有三种引用方式:左值引用(引用的是左值:可以取地址)、常性左值引用/万能引用(既可以引用左值:可以取地址,又可以引用右值:不可以取地址)、右值引用(引用的是右值:不可以取地址),当存在三个引用的函数时,我们应明确编译器的匹配顺序,根据实参的类型进行匹配!!!
        对于左值,优先匹配左值引用,其次才是常性左值引用!
        对于常性左值,直接匹配常性左值引用,不存在,编译器直接报错!
        对于右值引用,优先匹配右值引用,其次才是常性左值引用!

  1.  对于普通的变量(它是左值),编译器首先优先匹配形参为左值引用的函数,如果不存在该函数,然后,匹配形参为常性左值引用(常引用)的函数,如果二者都不存在,编译器直接报错!
  2.  对于常变量(const修饰),编译器直接匹配形参为常性左值引用(常引用)的函数,如果该函数不存在,编译器直接报错(不能匹配参数为右值引用的函数,右值引用引用的是右值)!
  3.  对于右值,编译器首先优先匹配形参为右值引用的函数,如果该函数不存在,然后,匹配形参为常性左值引用(常引用)的函数,如果二者都不存在,编译器直接报错!

7.注意函数调用的二义性; 如果在两个函数的参数表中,形参类型相同,而形参个数不同,形参默认值将会影响函数的重载.

#include<iostream>
using namespace std;

/****函数调用的二义性****/
void fun(int a);
void fun(int a, int b);
void fun(int a, int b = 10);

int main()
{
   fun(12);    //这里第一个函数会和第三个函数会发生冲突,编译器不知道该调用哪一个函数
   fun(12,13); //这里第二个函数会和第三个函数会发生冲突,编译器不知道该调用哪一个函数
  
   return 0;
}

3.3 函数重载解析的步骤

  1. 确定函数调用考虑的重载函数的集合,确定函数调用中实参表的属性(由形参表的属性确定调用那个函数)。
  2. 从重载函数集合中选择函数,该函数可以在(给出实参个数和类型)的情况下可以调用函数。
  3. 选择与调用最匹配的函数。

3.4 函数重载判断练习


#include<iostream>
using namespace std;

* /*******下面两个函数构成重载***********/
void func(int *p) {}         //可读可写
void func(const int *p) {}  //只可读,不可解引用修改


int main()
{
   int x = 10;         //普通变量:可读可写
   func(&x);           //优先匹配普通指针的函数(第一个),其次才是const修饰的指针的函数(第二个)

   const int y = 10;   //常变量:只可读
   func(&y);          //只能匹配const修饰的指针的函数(第二个),如果不存在该函数,直接报错!

   return 0;
}
#include<iostream>
using namespace std;
/*******下面两个函数不构成重载***********/

 //编译器会报错,这不是函数的重载,第二个在编译的时候会直接忽略const ,认为是同一个函数重复声明
 void func(int *p) {}            //可读可写,可以解引用修改数据
 void func( int* const p) {}    //可读可写,可以解引用修改数据,const修饰指针自身的时候,编译器可以忽略const的存在

 /**如果修改第二个为:void func( const int* const p) {}   那么这便又是函数的重载**********/


 int main()
 {
    int x = 10;    //普通变量
    func(&x);     //两个函数功能一样,对传入的数据都可读可写,匹配两个都可以!编译器不知道调用哪个,就会报错,认为是同一个函数重复声明!

    return 0;
 }

#endif
#include<iostream>
using namespace std;

/*****下面两个函数是重载函数:参数的个数不同******/

 void func(int a);
 void func(int a,int b);

 int main()
 {
	 func(12);
	 func(12, 23);
 }
#include<iostream>
using namespace std;
 
void func(int a);
void func(int a, int b=20);

int main()
{
	 func(12);    //这里第一个函数会和第二个函数会发生冲突,编译器不知道该调用哪一个函数
	 func(12, 23);//这里第一个函数会和第二个函数会发生冲突,编译器不知道该调用哪一个函数
}

由于函数调用的二义性,不是重载函数,这种问题只要不调用函数就不会报错,但是只要调用就会出现编译错误!!!

3.5 C++支持函数重载的原理-名字修饰(name Mangling)

3.5.1 预备知识

       “C"或者"C++”函数在内部(编译和链接)通过修饰名识别。修饰名是编译器在编译函数定义或者
原型时生成的字符串。修饰名由函数名、类名、调用约定、返回值类型、参数表共同决定。不同的调用方式,形成的修饰名也不同。

  1. _stdcall调用:Pascal程序的缺省调用方式,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。
  2. C调用(即用_cdecl 关键字说明):按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数的函数只能使用该调用约定)。
  3. _fastcall 调用:"人”如其名,它的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字( DWORD )或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同。
  4. thiscall调用:仅仅应用于“C++ "类的成员函数。this 指针存放于ECX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。

在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。

  1.  实际项目通常是由多个头文件和多个源文件构成,而通过C语言阶段学习的编译链接,我们 可以知道,【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么怎么办呢?
  2. 所以链接阶段就是专门处理这种问题,链接器看到a.o调用Add,但是没有Add的地址,就会到b.o的符号表中找Add的地址,然后链接到一起。
  3. 那么链接时,面对Add函数,链接接器会使用哪个名字去找呢?这里每个编译器都有自己的 函数名修饰规则。

3.5.2   C语言编译时函数名修饰约定规则(Windows平台下)

       C语言的名字修饰规则非常简单,_ cdec是C/C++的缺省调用方式,调用约定函数名字前面添加了下划线前缀。

_stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个"@”符号和其参数的字节数。

_fastcall调用约定在输出函数名前加上一个” @ "符号,函数名后面也是一个" @ "符号和其参数的字节数。

3.5.2   C语言编译时函数名修饰约定规则(Linux平台下)

通过下面我们可以看出gcc的函数修饰后名字不变。

3.5.3  C++编译时函数名修饰约定规则(Windows平台下)

3.5.3  C++编译时函数名修饰约定规则(Linux平台下)

3.6 C++函数重载的名字粉碎(名字修饰)

面试题:     

         编译器编译完是按照修饰名访问这些函数的,不同的调用方式,编译器编译时函数名修饰约定规则不同。

1、什么是函数重载?
     函数名相同而参数列表不同(类型或者数量),叫函数重载!
2、返回值可不可以作为函数重载的依据?
    不可以!C++的函数调用解析机制仅基于参数类型和数量来匹配函数,而不考虑返回值类型!如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。

3、为什么C++可以进行函数的重载,而C语言不可以?
     原因在于:二者的修饰名规则不同!(名字粉碎技术不同), C语言只是在函数名的 前面加个下划线,那么对于多个同名函数,他们的修饰名都相同,都是在函数名前面加个下划线!编译器在编译阶段无法区分!C++把形参列表的类型和数量作为修饰名的一部分,这样相同的函数名(函数重载)就会得到不同的修饰名,这样编译器在编译阶段就可以区分出来!

3.7 如何指定函数以C方式修饰函数名还是以C++方式修饰函数名


 extern "C" int add(int x, int y) { return x + y; }    //<==>  按照C的修饰规则,修饰名为:_add
double add(double x, double y) { return x + y; }//<==>  按照C++的修饰规则,修饰名为:?add@@YAHHH@Z
//上面编译可以通过,因为C和C++修饰名规则不同,编译器可以区分!


extern "C" int    add(int x, int y) { return x + y; }    //<==>按照C的修饰规则,修饰名为:_add
extern "C" double add(double x, double y) { return x + y; }  //<==>按照C的修饰规则,修饰名为:_add
//上面编译不可以通过,因为二者使用的都是C的修饰名规则,编译器无法区分!



//如何将一批函数指定相同的修饰规则:加个大括号
extern "C" 
{
     int add(int x, int y) //<==>  按照C的修饰规则,修饰名为:_add
     { 
	      return x + y;
     }    
     double add(double x, double y)  //<==>  按照C的修饰规则,修饰名为:_add
     { 
	    return x + y; 
     }   
}

四、函数模板(重点)

4.0 产生的原因   

       为了代码重用, 代码就必须是通用的;通用的代码就必须不受数据类型的限制那么我们可以把
数据类型改为一个设计参数。这种类型的程序设计称为参数化(parameterize) 程序设计。

        软件模块由模板构造。包括函数模板(function template)和类模板(class template)。

函数模板可以用来创建一个通用功能的函数, 以支持多种不同形参, 简化重载函数的设计

4.1 函数模板定义

       <模板参数表> 尖括号中不能为空,参数可以有多个,用逗号分开。模板参数主要是模板类型参数。模板类型参数代表一种类型,由关键字class 或typename 后加一个标识符构成,在这里两个关键字的意义相同,它们表示后面的参数名代表一个潜在的内置或用户设计的类型。
 

#include<iostream>
using namespace std;

 template<class T>      
 T my_max(T a,T b)
 {
	 return a > b ? a : b;
 }

 int main()
 {
	 my_max(12, 23);
	 my_max('a', 'b');
	 my_max(12.23, 34.45);
	 return 0;
 }

         编译阶段,编译器根据实参类型自动的生成如下函数代码:这也叫模板实参推演,其本质上还是函数重载,由编译器自动生成代码。

 typedef int T;
 T my_max<int>(T a, T b)
 {
	 return a > b ? a : b;
 }

 typedef char T;
 T my_max<char>(T a, T b)
 {
	 return a > b ? a : b;
 }


 typedef double T;
 T my_max<double>(T a, T b)
 {
	 return a > b ? a : b;
 }

在编译过程中,根据函数模板的实参构造出独立的函数,称为模板函数。这个构造过程被称为模板实例化。
 

#if 0
#include<iostream>
using namespace std;
#include <typeinfo>

template<class T>
void print(T a)
{
	cout << typeid(a).name() << endl;
	cout << typeid(a).name() << endl;
	cout << typeid(a).name() << endl;

}

int main()
{
	int a = 10;
	int arr[] = { 1,2,3,4,5 };
	print(a);        //编译阶段,编译器推演出:T为整型 int
	print(&a);       //编译阶段,编译器推演出:T为整型指针 int *
	print(arr);      //数组名代表首元素的地址,也就是一个指针,编译阶段,编译器推演出:T为整型指针 int *
}
#endif

4.2 数组的推演及引用的推演

#if 1
#include<iostream>
using namespace std;
#include <typeinfo>
template<class T>
void print(T &a)                              //函数的参数为实参类型的引用(实参的引用)
{
	cout << typeid(T).name() << endl;
	cout << typeid(a).name() << endl;
	cout << sizeof(a) << endl;              //20

}

int main()
{
	int a = 10;
	int arr[] = { 1,2,3,4,5 };
	print(a);                                 //编译阶段,实参a为一个整型, 编译器推演出:T为整型 int ,函数参数a为整型引用类型:int &
	print(&a);                               //编译阶段,编译器无法推演,实参为一个整型指针也就是:int *, 所以a就是这个整型指针的引用,按道理推演a为:int* & ,在实参里面可以a++,但是这里的&a是一个地址,是一个常量(带有常性const),所以编译器无法推演!,
	print(arr);                              //编译阶段,数组名代表首元素的地址,也就是一个整型指针,编译阶段,编译器推演出:数组引用,int [5]&
}

4.3 利用函数模板实现泛型编程


下面这个打印数组的函数代码适用于一切类型的数组,我都可以打印整个数组,类型由编译器自动推演

#include<iostream>
using namespace std;

// typedef int T      ==>int
// #define N  5        ==>5

 template<class T, int N>
 void print(T(&br)[N])            /*模板类型参数推演时:类型是重命名规则,非类型是宏的规则直接拿数值替换*/
 {
	 for (int i = 0; i < N; i++)
	 {
		 cout << br[i] << " ";

	 }
	 count << endl;
 }

 int main()
 {
	 int arr[5] = { 1,2,3,4,5 };
	 double brr[3] = { 1.2,3.4,5.3 };
	 print(arr);               //数组引用:int (&br)[5]=arr;
	 print(brr);               //数组引用:double (&br)[3]=brr;
 }

4.4 模板函数的重载与特化(完全特化、部分特化、泛化)

  1.  模板函数的重载是C++中允许通过模板实现多个函数版本的功能。在C++中,函数可以通过相同的名字但不同的参数列表来重载。模板函数也可以通过这种方式实现重载。
  2.  函数模板特化是指为特定类型提供一个专门的模板实现,与上面相比,而不是使用通用的模板版本。
#include<iostream>
using namespace std;

 /*泛化:无任何限制*/
 template<class T>
 void func(T a)
 {

 }
 

 /*部分特化:限制常性的任意类型指针*/
 template<class T>
 void func(const T *p)
 {

 }


 /*完全特化:限制常性的字符类型指针*/
 void func(const char* p)            
 {

 }


 int main()
 {
	 const char* str = "hello";
	 int a = 10;
	 func(a);
	 func(&a);
	 func(str);

	 return  0;
 }

4.5 类模板

         类模板是C++中一种强大的工具,用于定义通用的类。通过类模板,可以创建能够处理任意数据类型的类,而无需为每种数据类型编写单独的类。类模板使用模板参数来表示类中的数据类型,使得类的实现能够适用于多种类型。容器是一种数据结构,用于存储和管理一组对象。在C++标准库(STL,Standard Template Library)中,容器是实现了特定接口的类模板。这些类模板提供了存储、访问和操作其包含的元素的功能。

C++容器库包括序列容器、关联容器和无序容器。
     序列容器(Sequence Containers):用于按照线性顺序存储数据。

  1. std::vector:动态数组,支持快速随机访问和在末尾插入 / 删除元素。
  2. std::deque:双端队列,支持快速随机访问和在两端插入 / 删除元素。
  3. std::list:双向链表,支持在任何位置快速插入 / 删除元素。
  4. std::array:固定大小的数组,大小在编译时确定。
  5. std::forward_list:单向链表,支持在任何位置快速插入 / 删除元素,但只允许单向遍历。

 容器库的特点

  1. 泛型编程:通过模板实现,容器可以存储任意类型的对象。
  2. 自动管理内存:容器会自动管理其所需的内存,开发者无需手动分配和释放内存。
  3. 迭代器支持:所有容器都提供迭代器,用于遍历和操作元素。
  4. 算法兼容性:STL中的算法可以与容器无缝配合使用,提供诸如排序、搜索、复制等操作。
使用类模板生成任意类型的顺序栈

template<class T>

class SeqStack
{
  private:
	   T* data;
	   int top;

  public:
	  SeqStack(int sz = 100)
	  {
		data = (T*)malloc(sizeof(T) * sz);
		top = -1;
	  }
};



int main()
{
	SeqStack<int> ist;          //类模板的类型必须给定
	SeqStack<double> dst;

}

/*******编译器会根据上述模板生成如下代码:**/

class SeqStack<int>
{
	typedef int T;
    private:
	   T* data;
	   int top;

    public:
	   SeqStack(int sz = 100)
	   {
		  data = (T*)malloc(sizeof(T) * sz);
		   top = -1;
	   }
};


class SeqStack<double>
{
	typedef double T;
    private:
	   T* data;
	   int top;

    public:
	   SeqStack(int sz = 100)
	   {
		  data = (T*)malloc(sizeof(T) * sz);
		  top = -1;
	   }
};

五、名字空间:namespace

5.1 C++作用域的划分

        在C++中把作用域划分为:全局作用域,局部作用域、块作用域,名字空间作用域和类作用域。注意:作用域是针对编译器来说的,生存周期针对运行的时候来说的。函数被调用时,被调用函数内部的变量才会分配内存空间,当函数执行完毕,被调用函数内部的变量将会归还给操作系统。我们定义的变量存储在栈区或者堆区。

  1.  全局变量:函数之外定义的变量:存储在数据区,
  2.  局部变量:函数内部定义的变量:存储在栈区,
  3.  静态局部变量:函数内部定义的变量加static关键字修饰,只创建一次,保存上次的值,不会重新初始化:存储在数据区(字符串常量也是)
  4.  花括号内部的变量,只在花括号内部有效:存储在栈区。
  5.  类作用域是指在类定义内部的作用域,决定了类中的成员(包括成员变量和成员函数)的可见性和生命周期。
  6.  名字空间作用域:多文件编程时:多个源文件定义相同的全局变量名字或者函数名,在项目进行编译链接时候就会发生全局命名冲突!名字空间域是随标准C++而引入的。它相当于一个更加灵活的文件域(全局域)。

5.2  命名空间

       在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存 在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化, 以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。

       名字空间域的引入,主要是为了解决全局名字空间污染(global namespace pollution)问题,即防止程序中的全局实体名与其他程序中的全局实体名的命名冲突。于是,便产生了命名空间。

5.3 命名空间的定义

       定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{} 中即为命名空间的成员。

// 1. 正常的命名空间定义
namespace p1
{
 // 命名空间中可以定义变量/函数/类型
    int rand = 10;
    int Add(int left, int right)
    {
        return left + right;
    }

    struct Node
    {
      struct Node* next;
      int val;
    };
}


//2. 命名空间可以嵌套
// test.cpp
namespace N1
{
   int a;
   int b;
   int Add(int left, int right)
   {
        return left + right;
   }
   namespace N2
   {
       int c;
       int d;
       int Sub(int left, int right)
       {
         return left - right;
       }
   }
}

//3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
// ps:一个工程中的test.h和上面test.cpp中两个N1会被合并成一个
// test.h
namespace N1
{
    int Mul(int left, int right)
    {
       return left * right;
    }
}

注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中

5.4 命名空间使用

       命名空间中成员该如何使用呢?比如:

5.4.1 加命名空间名称及作用域限定符

int main()
{
    int a = yhp: :my_ add(12,23) ;
    printf("%1f \n",Primer: :pi);
    printf ("%f \n",yhp: :pi);
    Primer: :Matrix: :my_ _max('a','b');
    return 0;
}

5.4.2 使用using将命名空间中某个成员引入

   using yhp::pi;
   using Primer: :Matrix: :my_max;
//名字空间类成员matrix的using声明
//以后在程序中使用matrix时,就可以直接使用成员名,而不必使用限定修饰名。
int main()
{
    printf("%1f \n",Primer: :pi) ;
    printf("%f \n",pi); // yhp::
    my_max('a','b');
    return 0;
}

5.4.3 使用using namespace 命名空间名称引入

        使用using指示符可以一次性地使名字空间中所有成员都可以直接被使用,比using声明方便。using指示符;以关键字using开头,后面是关键字namespace,然后是名字空间名。
using namespace名字空间名;

using namespace yhp;
int main()
{
    printf("%1f n",Primer: :pi ) ;
    printf("%f n" ,pi) ;// yhp: :
    my_add(12,23); // yhp::
    return 0;
}

多文件结构示例
 

std命名空间的使用惯例: .
       std是C++标准库的命名空间,如何展开std使用更合理呢?
1.在日常练习中,建议直接using namespace std即可,这样就很方便。
2. using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对
象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模
大,就很容易出现。所以建议在项目开发中使用,像std::cout这样使用时指定命名空间+
using std::cout展开常用的库对象/类型等方式。

 

        这篇博客内容较为丰富,为后续学习好C++做好准备, 如果对此专栏感兴趣,点赞加关注! 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1890139.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux应用---内存映射

写在前面&#xff1a; 在进程间通信中&#xff0c;有一种方式内存映射。内存映射也是进程间通信的方式之一&#xff0c;其效率高&#xff0c;可以直接对内存进行操作。本节我们对内存映射进行学习&#xff0c;并结合案例进行实践。 1、基本理论 内存映射&#xff1a;是将磁盘文…

日期和时区

日期 时区 修改时区可分为两步 删除系统自带的 localtime 文件 rm -f /etc/localtime 将系统中内置的 Shanghai 文件软连接到 /etc/localtime中 sudo ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

Softing助力工业4.0 | 通过OPC UA和MQTT访问SINUMERIK 840D CNC控制器数据

Softing uaGate 840D是用于采集西门子SINUMERIK 840D SL/PL CNC控制器数据的物联网网关&#xff0c;支持OPC UA服务器和MQTT发布功能。该网关提供对SINUMERIK 840D CNC控制器机床数据的访问&#xff0c;支持读取、处理重要的主轴和从轴数据&#xff0c;例如扭矩和功耗&#xff…

第一周题目总结

1.车尔尼有一个数组 nums &#xff0c;它只包含 正 整数&#xff0c;所有正整数的数位长度都 相同 。 两个整数的 数位不同 指的是两个整数 相同 位置上不同数字的数目。 请车尔尼返回 nums 中 所有 整数对里&#xff0c;数位不同之和。 示例 1&#xff1a; 输入&#xff1a…

16:9横屏素材去哪里找?优秀的横屏视频素材资源网站分享

在这个视频内容快速发展的时代&#xff0c;寻找合适的视频素材变得尤为重要。以下是几个优秀的16:9横屏视频素材提供网站&#xff0c;无论您是视频制作新手还是资深专家&#xff0c;这些网站都能为您的视频项目增添光彩。 蛙学网 蛙学网作为视频创作者们广为人知的一个平台&am…

茗鹤 | 如何借助APS高级计划排程系统提高汽车整车制造的效率

在我们做了详尽的市场调研及头部汽车制造企业排程需求沟通后&#xff0c;我们发现尽管企业有很多的业务系统做支撑&#xff0c;在计划排程领域&#xff0c;所有的汽车制造总装厂仍旧使用人工“Excel”做排产规划&#xff0c;其中少部分也会借助MRP、第三方辅助排产工具。鉴于我…

科研与英文学术论文写作指南——于静老师课程

看到了一个特别棒的科研与英文学术论文写作指南&#xff0c;理论框架实例。主讲人是中科院信息工程研究所的于静老师。推荐理由&#xff1a;写论文和读论文或者讲论文是完全不一样的&#xff0c;即使现在还没有发过论文&#xff0c;但是通过于老师的课程&#xff0c;会给后续再…

使用 Ollama 时遇到的问题

题意&#xff1a; ImportError: cannot import name Ollama from llama_index.llms (unknown location) - installing dependencies does not solve the problem Python 无法从 llama_index.llms 模块中导入名为 Ollama 的类或函数 问题背景&#xff1a; I want to learn LL…

车载测试之-CANoe创建仿真工程

在现代汽车工业中&#xff0c;车载测试是确保车辆电子系统可靠性和功能性的关键环节。而使用CANoe创建仿真工程&#xff0c;不仅能够模拟真实的车辆环境&#xff0c;还能大大提升测试效率和准确性。那么&#xff0c;CANoe是如何实现这些的呢&#xff1f; 车载测试中&#xff0…

2024年7月3日 (周三) 叶子游戏新闻

老板键工具来唤去: 它可以为常用程序自定义快捷键&#xff0c;实现一键唤起、一键隐藏的 Windows 工具&#xff0c;并且支持窗口动态绑定快捷键&#xff08;无需设置自动实现&#xff09;。 卸载工具 HiBitUninstaller: Windows上的软件卸载工具 《魅魔》新DLC《Elysian Fields…

Flink 窗口触发器(Trigger)(一)

Flink 窗口触发器(Trigger)(一) Flink 窗口触发器(Trigger)(二) Flink的窗口触发器&#xff08;Trigger&#xff09;是流处理中一个非常关键的概念&#xff0c;它定义了窗口何时被触发并决定触发后的行为&#xff08;如进行窗口数据的计算或清理&#xff09;。 一、基本概念 …

node与npm安装教程

node与npm的下载安装教程&#xff1a; 文章目录 node与npm的下载安装教程&#xff1a;---Node.js 介绍NPM 介绍 一&#xff1a;下载&#xff08;node与npm的安装包是在一起的&#xff09;二&#xff1a;安装1&#xff1a;双击运行安装文件1-node-v14.15.0-x64.msi,点击下一步。…

区块链加载解析方法

一.区块链加载解析 对于数据的下载主要包括三种方式&#xff1a; 1.实现比特币网络协议&#xff0c;通过该协议和其他比特币全节点建立联系&#xff0c;然后同步区块数据。 2.通过比特币节点提供的API服务下载区块链数据。 3.通过blickchain.com提供的rest服务下载区块数据…

windows 屏幕录制录屏;gif工具推荐;滑动截屏

1、gif工具推荐gif123 参考&#xff1a;https://gif123.aardio.com/ 很小&#xff0c;很简洁 2、滑动截屏 参考&#xff1a;https://shipinzan.com/ll-gd-jp.html 通过google、edge浏览器 3、windows 屏幕录制录屏 1&#xff09;WinG 2)笔记本 prtsc 按键 可以截图&…

ResNet50V2

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、ResNetV1和ResNetV2的区别 ResNetV2 和 ResNetV1 都是深度残差网络&#xff08;ResNet&#xff09;的变体&#xff0c;它们的主要区别在于残差块的设计和…

如何对低代码平台进行分类?

现在市面上的低代码平台就像雨后春笋一样冒出来&#xff0c;而且源源不绝&#xff0c;但总结下来&#xff0c;大致的也就以下三类。 一、 aPaaS多引擎类&#xff08;有很多成熟引擎、做好东西要一起用&#xff09; 这类产品包括&#xff1a;织信Informat&#xff08;国内&…

多模态图像生成的突破:Image Anything一种无需训练的智能框架

多模态图像生成是内容创作领域的热点技术&#xff0c;尤其在媒体、艺术和元宇宙等领域。该技术旨在模拟人类的想象力&#xff0c;将视觉、文本和音频等多种模态属性相关联&#xff0c;以生成图像。早期的方法主要侧重于单一模态输入的图像生成&#xff0c;例如基于图像、文本或…

C++部分复习笔记下

7. C11 范围for 使用格式 vector<int> v { 1,2,3,4,5 }; for (auto e : v) {cout << e << " "; } cout << endl;底层原理&#xff0c;使用迭代器 vector<int> v { 1,2,3,4,5 }; auto it v.begin(); while (it ! v.end()) {cout…

项目2:API Hunter 细节回顾 -1

一. 接口调用 对于开发者来说&#xff0c;接口的调用应当是方便快捷的&#xff0c;而且出于安全考虑&#xff0c;通常会选择在后端调用第三方 API&#xff0c;避免在前端暴露诸如密码的敏感信息。 若采用 HTTP 调用方式&#xff1a; HttpClientRestTemplate第三方库&#xf…

kaggle量化赛金牌方案(第七名解决方案)(下)

— 无特征工程的神经网络模型&#xff08;得分 5.34X&#xff09; 比赛进入最后阶段&#xff0c;现在是时候深入了解一些关于神经网络模型的见解了。由于 Kaggle 讨论区的需求&#xff0c;我在这里分享两个神经网络模型。第一个是 LSTM 模型&#xff0c;第二个是卷积网络&…