当前位置:网站首页 > C++编程 > 正文

c++中数组合并(c++数组合并函数)



本篇记录使用到的C++知识。

在C++语言中,当我们使用基类的引用或者指针调用一个虚成员函数时,会进行动态绑定。因为我们直到运行时才能知道到底调用了哪个版本的虚函数,所以所有虚函数都必须有定义

OOP的核心思想就是多态性,我们把具有继承关系的多个类型称为多态类型,同时引用或指针的静态类型与动态类型不同这个事实,正是C++语言支持多态性的根本存在。

​ 当我们在派生类中覆盖了某个虚函数时,可以再一次使用关键字指出该函数的性质,然而这么做并非必须,因为一旦某个函数被声明为虚函数,则在派生类中它都是虚函数

和和类似,如果子类重写了父类中的方法,一般加上一个关键字,来进行说明。在C++ 11中就提供了这么个关键字,我们可以在派生类中用来说明派生类中的虚函数,作用:

  1. 可以使得程序员的意图更加清晰;
  2. 可以让编译器为我们发现一些错误,如果使用标记了某个函数,但是该函数没有覆盖已存在的虚函数,则编译器会报错。

可以在函数体的位置(声明语句的分号之前)书写,将一个虚函数说明为纯虚函数。含有纯虚函数的类为抽象基类,抽象基类负责定义接口,后续的其他类可以覆盖该接口,不能直接创建一个抽象基类的对象。

是Qt中的一个关键字,用于在C++中声明一个成员函数可以从中调用。即使用修饰的成员函数,可以被中的代码作为一个可调用项使用(类似函数),这使得开发者可以在中直接调用C++的成员函数

使用时,需要注意下面几点:

  • 被修饰的函数必须是类的成员函数而非静态函数;
  • 被修饰的函数必须是公有函数;
  • 被修饰的函数不能是虚函数;
  • 被修饰的函数的参数和返回值必须是Qt数据类型或者类型;
  • 被修饰的函数不能接收或返回用户自定义类型的指针,但是可以将用户自定义类型放入中传递。

使用修饰的函数通常定义在显示的数据模型中,以便界面能够直接调用数据模型相应的成员函数对数据进行处理。

预处理阶段编程的操作目标是"源码",用各种预处理指令控制预处理器,把源码改造成另一种形式。C++的关键字非常多,但是预处理指令很少,常用的也就、、,下面来简单说几点:

  • 预处理指令都以开头,虽然都在一个源文件中,它不属于C++语言,走的是预处理器不受C++语法规则约束
  • 预处理指令不应该受C++代码缩进影响,不论在哪里,永远都定格写
  • 单独一个也是一个预处理指令,叫做空指令,可以当做特别的预处理空行。而且与后面的指令之间也可以有空格,从而可以实现缩进
  • 预处理程序无法调试,可以使用的选项,来输出预处理后的源码。
 
  

预处理指令的作用是包含文件,而不是包含头文件,是可以包含任意文件。只要愿意,可以把源码、文本,甚至图片都包含进来。所以功能很弱,只知道把数据合并进源文件。

这也是为什么我们在写头文件时,需要加上"Include Guard"机制代码,来防止头文件被重复包含。

 
  

除了包含头文件,还可以利用 实现代码片段共享,比如我们可以把一些数据片段放入一个文件中,然后使用实现源码级别的功能抽象。

 
  

在C++中,文件没有特定的类型或规定,一般来说,文件是用于包含实现代码的文件,不同于和文件。文件通常定义C++类或函数的声明,这些声明提供了类或函数的名称、参数和返回类型等信息。而文件则包含这些声明所对应的函数实现。

文件与之类似,但通常将其用于实现C++模板类或关联函数,由于编译器的限制,也无法被编译。所以通常被看成一种辅助文件,提供一种共享代码的方式,比如上面配合的使用。

使用可以定义一个源码级别的"文本替换",也就是宏定义。功能非常强大,在预处理阶段可以无视C++语法限制,进行替换任何文字,定义常量/变量,实现函数功能,减少重复代码等等。

也由于宏太灵活,使用宏时一定要谨慎,时刻记者以简化代码、清晰易懂为目的,不能滥用,导致代码难以阅读。下面有几点注意事项:

  1. 宏的展开和替换发生在预处理阶段,不涉及函数调用、参数传递、指针寻址,没有任何运行时期效率损失。所以对于经常调用的小代码片段,使用宏来封装比inline关键字更好,是真正源码级别的内联。
  2. 宏是没有作用域概念的,永远都是全局有效。所以对于一些用来简化代码、起临时作用的宏,最好在用完之后使用取消宏定义。
  3. 另一种做法是宏定义前先检查,如果之前有定义,就先进行,然后再重新定义。
  4. 适当使用宏定义可以消除代码中的常量,消除"魔数数字"、"魔数字符串"。

记住预处理器的精髓,它是用来处理源码的,所以利用前面定义的各种宏,我们可以在预处理阶段实现分支处理,通过判断宏的数值,来产生不同的源码,从而改变源文件的形态,这就是"条件编译"。

条件编译有2个重点,一个是条件指令,另一个是后面的判断依据,也就是定义好的各种宏,而这个判断依据是条件编译中非常关键的部分。

既然是预处理阶段,所以通常在编译环境中会预定义一些宏,这样预处理器就可以在预处理阶段对源码做处理,从而产生符合当前系统或者编译器等的源码。比如有一个宏是,它标记了C++语言版本号,我们可以使用它来判断当前是C还是C++,比如下面代码:

 
  

这段代码不太容易理解,假如定义了,则说明是用C++编译,这时需要按照C的方式去处理,就需要添加,否则就不添加。所以这里使用了2处判断,按照是否是C++编译会生成2种源码,这就是源码级的条件编译。

数组类型在C/C++的世界中,只能是"二等公民",只可以用在栈变量和结构体/类中的字段,而不能用在函数的返回值上,用在函数参数上也容易造成误解。

这不是技术原因,更像是C语言设计失误。C是一种简单的语言,当初为了语言使用方便,有2个简化行为:函数会变成函数指针,数组会变成元素的指针。现在来看,这种设计是有问题的,但是在C这样的简单语言中,这种规则可以带来方便,只是C++为了兼容性,就继承了这些规则。

还有一个角度为什么解释了C++为什么不能返回数组。因为数组不能直接拷贝,C++函数能返回对象,要不对象分配在堆里面,要不会生成一个临时对象,才可以返回。而对于返回值和指针,可以直接通过寄存器返回。对于数组而言,没有拷贝构造函数,不方便返回。

这里的直接拷贝赋值是使用符号来进行是不允许的。比如和,这里的和的类型是常量指针类型,常量是不能对其进行赋值的。

理解这句话要明白前面所说的,数组会退化为元素指针,同时常量指针是无法重新赋值。同时我们应该知道,数组的拷贝用其他方式可以,但是这种直接的方式不行,不是技术原因,而是历史原因。

C++中的,表示"常量",最常用的用法是定义程序用到的数字、字符串常量,代替宏定义,比如下面代码:

 
  

从C++程序的生命周期来看,它和宏有着本质区别:定义的常量在预处理阶段并不存在,而是直到运行阶段才会出现。

所以,它其实是运行时的"变量",只是不能修改,是"只读"的(read only),所以常量叫做"只读变量"其实更合适

由于指针本身是一个对象,它又指向另一个对象,因此,指针本身是不是常量以及指针所指的是不是一个常量就是2个互相独立的问题。用名词顶层表示指针本身是一个常量,而用名词底层表示指针所指的对象是一个常量

有着更一般的定义,顶层可以表示任意的对象是常量,对任何数据类型都适用,比如算术类型、类、指针等。而底层只和指针和引用等复合类型的基本类型部分有关。但是指针,既可以是顶层,也可以是底层。

更难的点,是关于这2种的书写形式,常见如下:

 
  

正常来说,我们的书写形式都是放在类型前面,对于值类型来说,就表示值本身只读,是顶层,而对于指针和引用来说,这也是最正常的用法,指向只读变量的指针或绑定到只读变量的引用。

只有一种写法比较奇特,就是写在后面,表示指针自己是只读的。

理解这一点非常关键,因为函数的参数和返回值都涉及到拷贝,而且还和函数重载有关。对于顶层来说,几乎不受什么影响:

 
  

但是对于底层则不能忽略,当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层资格,或者可以转换:

 
  

这里的转换,有一个非常有意思的理解:所谓指向常量的指针或者引用,不过是指针或者引用的"自以为是"罢了,它们觉得自己指向了常量,所以直觉地不去改变所指对象的值

说点实际使用的,根据前面理解,对于一个顶层来说,它不会影响传入函数的对象,即一个拥有顶层的形参无法和一个没有顶层的形参区分开来,比如下面代码:

 
  

我们可以想象一下,调用该函数时,会发生值拷贝,即把实参拷贝给形参来完成初始化,拷贝完后这个形参在其作用域中改不改变,其实是无所谓的,所以这2个函数是重复声明。同时,指针也可以是顶层,如下:

 
  

一样的道理,在函数调用时也是发生值拷贝,拷贝进来一份地址数据,而对于形参来说,它自己的值(即地址)可不可以改变,其实是无所谓的,所以是重复声明。

而如果形参是引用或者指针,则可以通过区分其指向的是常量对象还是非常量对象可以实现函数重载,这时的是底层的。比如下面代码:

 
  

记住一个原则,函数实参和形参的初始化规则和之前变量初始化规则一样。首先就是我们不能把一个常量引用赋值给一个非常量的引用,这样函数可以修改这个常量,明显不符合常理。其次,我们也不能把一个常量指针赋值给一个非常量指针,这样的话在函数内,可以通过这个非常量指针可以修改常量,也是不符合常理。

所以,上面4个重载函数都是对的。还有一点需要注意,虽然不能转换成其他类型,我们只能把对象(或者指向的指针)传递给形参,但是非常量可以转换成,即上面4个函数都可以作用于非常量对象或指向非常量对象的指针。不过,当我们传递一个非常量或者指向非常量对象的指针时,编译器会优先选用非常量版本的函数

只能改变运算对象的底层,而底层不外乎就是指针和引用,这是一种"去掉性质"的行为,一旦我们去掉了某个对象的性质,我们就可以对对象进行写操作了。

这个特性很奇怪,既然要去掉底层,在开始声明时,不加不就可以了?对于底层是想去就去吗?会不会导致错误?

首先,如果一个对象本身就是常量,再使用执行写操作会产生未定义的后果。这句话就有点反常识,比如下面代码:

 
  

这就是典型的问题代码,是未定义行为。的目的是某些变量原本不是的,但是由于某种特殊原因,变成了的,比如下面代码:

 
  

在这种情况下,代码才是可以正常执行的。

的使用场景非常少,其中一个就是重载。前面说了,对于底层类型不同的参数,是可以重载函数的,比如有下面函数:

 
  

由于是比较字符串长度,我们并不需要修改字符串,所以这个函数的参数和返回值类型都是的引用。但是当我们对2个非常量的调用该函数时,根据非常量可以转成,这里依旧可以调用,并且返回还是的引用。

这种结果有一点不太好,因此我们需要一种新的函数,当实参不是常量时,得到一个普通引用,这时我们可以使用来实现:

 
  

在这个实现中,首先把非类型的实参转换为类型,注意这里是增加其底层特性,然后返回类型的引用,最后再转换为非常量引用。这里之所以不会出错,是因为我们知道这里的和原来就是非的,即使对函数结果进行去行为也是正确的。

核心点还是只能处理对象的底层,且是在预期之内。

C++的语法还是很值的琢磨的,有一种集大成的感觉,这些基础知识需要夯实。

到此这篇c++中数组合并(c++数组合并函数)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!

版权声明


相关文章:

  • git clone和git pull的区别(git clone 和git checkout区别)2025-11-06 15:18:04
  • cnngb是哪个港口(cnxmn是哪个港口)2025-11-06 15:18:04
  • conv1d怎么读(convid怎么念)2025-11-06 15:18:04
  • tkdd期刊含金量(tdsc期刊)2025-11-06 15:18:04
  • cnns认证的检测机构(什么是cnas检测认证)2025-11-06 15:18:04
  • cpu参数对比工具(cpu参数对比网站)2025-11-06 15:18:04
  • githun镜像(git clone镜像)2025-11-06 15:18:04
  • TCP工具坐标系(TCP工具坐标系)2025-11-06 15:18:04
  • apc和aps经济学(aps经济学是什么意思)2025-11-06 15:18:04
  • cs700改风扇(7025b改风扇)2025-11-06 15:18:04
  • 全屏图片