zl程序教程

您现在的位置是:首页 >  后端

当前栏目

C++中的非常规关键字使用的总结

C++ 总结 关键字 使用
2023-09-14 09:15:04 时间

cpp中的非常规关键字(在一般小项目编程中不会刻意用到的关键字)有很多,使用这些非常规的关键字,目的就是三点:一、使代码变的更加灵活(如指针、template关键字、宏语法);二、使代码变得更加严谨(如const、static、explicit、virtual等,可消除在代码互动中的歧义和误改动,约束程序员对代码的用法,并消除在代码执行期间的bug);三、是代码执行更高效(如final、noexcept、constexpr等),让编译器在编译代码时可根据代码关键字进行优化。这里对cpp中的非常规关键字使用进行总结,具体包含:std指针、inline|static|final|const|typedef等一般关键字、define的用法、template的用;此外,还包括:constexpr、explicit、virtual、noexcept等特殊关键字的用法。

此外,c++还有更多的关键字,如关于变量的关键字(register[寄存器变量,更快的读取速度]、volatile[防止编译器优化,保持内存可见性])等(告诉编译器该变量的位置、是否需要进行优化)。具体可以参考https://blog.csdn.net/Jochebed666/article/details/90613927

1、std指针

在std库的中有auto_ptr,unique_ptr,shared_ptr和weak_ptr四种指针,在后续升级中auto_ptr智能指针被抛弃了(容易产生内存泄漏,auto_ptr a1赋值给auto_ptr a2后,同一个内存地址在auto_ptr的析构函数中会被释放两次)。

//声明
std::shared_ptr<Ort::Env>env;
//赋值
env.reset(new Ort::Env(ORT_LOGGING_LEVEL_WARNING, env_name.c_str()))
//释放
env->release();

具体可以参考 https://blog.csdn.net/love10_1314/article/details/94740707

std::ptr_fun(),将一个普通函数包装成函数对象。此函数与关联类型从 cpp11 起被弃用,被更通用的 std::function 和 std::ref 所替代。
cpp11 替用方案 :
可将 std::not1(std::ptr_fun(isvowel)) 改为 std::not1(std::cref(isvowel)) 或者 std::not1(std::function<bool(char)>(isvowel))
https://www.jianshu.com/p/1bbd8bac9fbb

2、关键字

inline修饰符

用于修饰函数的实现代码,表示为内联函数(用于避免 频繁调用的小函数大量消耗栈空间【栈内存】的问题),既在编译时代码在调用点展开(不从栈内存中加载函数)。inline修饰的函数是不能进行递归调用的,其本身只是带编译器的一个建议(编译器会自动根据函数的复杂程度决定是否要真正实现内联)。定义在类中的成员函数(被class{}包裹实现的函数)默认都是内联的;inline时一种用于描述实现的关键字,不能放在函数声明上。具体参考 https://www.runoob.com/w3cnote/cpp-inline-usage.html

static修饰符

在修饰变量的时候,static 修饰的静态局部变量只执行初始化一次(多次调用函数,其内部的static初始化只执行一次),而且延长了局部变量的生命周期(要在运行结束以后才释放);static 修饰全局变量的时候,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是 extern 外部声明也不可以。static 修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用。
在面向对象中,被 static 修饰的变量属于类变量,static 修饰的方法属于类方法,可以直接通过类名调用,不需要new出新实例;同时,被static 修饰的变量和函数在类级别是全局共享的。具体参考 https://www.runoob.com/w3cnote/cpp-static-usage.html

final修饰符

用于表示该修饰对象处于最终状态,不可以被再度修改(如修饰class时表示该类不可以被继承;修饰虚函数时表示该类不可以被重载)。可以包含特定类或函数,在实际使用中还可以用 final修饰虚函数,以提升代码执行性能(在动态绑定时[父类指针指向子类],一个类或函数声明成final,在编译器层面可以优化掉一步vtable的查找开销,这在cpp的标准中并没有规定,完全依赖于编译器的实现。)具体参考 https://zhuanlan.zhihu.com/p/104210081

const修饰符

const 是 constant 的缩写,本意是不变的,不易改变的意思。cpp const 允许指定一个语义约束,编译器会强制实施这个约束,允许程序员告诉编译器某值是保持不变的。如果在业务过程中有某个变量在初始化后就不能被修改,就应该明确使用const,这样可以获得编译器的帮助。 具体参考https://www.runoob.com/w3cnote/cpp-const-keyword.html

与const修饰相对应的是volatile修饰,表示该变量是经常被改变的,该关键字的作用是防止编译器优化时把变量从内存装入 CPU 寄存器中(CPU寄存器加载变量速度比内存快)。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。volatile 的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值。

typedef

使用关键字 typedef 可以为类型起一个新的别名。typedef 的用法一般为:

typedef  oldName  newName;

typedef 还可以给数组、指针、结构体等类型定义别名,具体例子如下所示:

typedef char ARRAY20[20];
ARRAY20 a1, a2, s1, s2;  //与下面一行等价
char a1[20], a2[20], s1[20], s2[20];

typedef 在表现上有时候类似于 #define,但它和宏替换之间存在一个关键性的区别,更多参考http://c.biancheng.net/view/2040.html

3、define的用法

又称为宏定义,其本质是实现字符串替换,可以用于实现各种常量、简单函数的定义。
其中关于宏函数(相比于函数调用可节省一定时间)实现max、min的效果可以参考 https://blog.csdn.net/Sleepp/article/details/81876502
利用宏函数实现字符串连接,在宏语法中 a ## b 的结果为ab,表示为连接

#define ACCESS _access
#define symbol  '\\'
#define max(a,b) ((a) > (b) ? (a) : (b))
#define min(a,b) ((a) < (b) ? (a) : (b))
#define SLEEP Sleep(1000)
#define mkdirs(path) _mkdir(path)
#define concat( x, y )  x ## y

条件编译,可以用于针对不同的的操作系统执行不同的代码

#ifdef WIN32
code for windows
#else
code for linux
#endif

可以用于避免hpp头文件函数实现被多次导入(#pragma once也是描述该头问题只用导入一次)

#ifndef __WebFun__
#define __WebFun__
#pragma once

code

#endif

在代码上指定添加某静态库

#pragma comment( lib, "opencv.lib" )   //指定与静态库一起连接

4、template的用法

template 是cpp中用于定义模板的固定格式。模板是实现代码重用机制的一种工具,它可以实现类型参数化,即把类型定义为参数, 从而实现了真正的代码可重用性。模版可以分为两类,一个是函数模版,另外一个是类模版。内容完全参考 LabVIEW_Python http://events.jianshu.io/p/90bad1ef3431

函数模板

可实现函数的泛化,避开函数类型的强制声明

//数据类型T 参数化数据类型
template <typename T>
void generic_swap(T& a, T& b)
{
    T tmp;
    tmp = b;
    b = a;
    a = tmp;
}
int main()
{
    int a = 100, b = 50;
    generic_swap(a, b);
    cout << "excute the swap():" << a << " : " << b << endl;


    char c = 'A', d = 'B';
    generic_swap(c, d);
    cout << "excute the swap():" << c << " : " << d << endl;
    
    string e = "Jacky", f = "Lucy";
    generic_swap(e, f);
    cout << "excute the swap():" << e << " : " << f << endl;

    double j = 1.314, k = 5.12;
    generic_swap(j, k);
    cout << "excute the swap():" << j << " : " << k << endl;
    
    return 0;
}

类模板

可在一个类函数中声明多个类模板,用于支持灵活调用,常见于vector、vector、vector等的使用,既在实例化对象后要显性的指定模板类型。

#include<iostream>
#include<string>
using namespace std;

//注意:模板头和类头是一个整体,可以换行,但是中间不能有分号
template<typename T1, typename T2>  //这里不能有分号
class Point {
public:
    Point(T1 x, T2 y) : m_x(x), m_y(y) { }
public:
    T1 getX() const;  //获取x坐标
    void setX(T1 x);  //设置x坐标
    T2 getY() const;  //获取y坐标
    void setY(T2 y);  //设置y坐标
private:
    T1 m_x;  //x坐标
    T2 m_y;  //y坐标
};

//下面就对 Point 类的成员函数进行定义
template<typename T1, typename T2> T1 Point<T1, T2>::getX() const {
    return m_x;
}

template<typename T1, typename T2> void Point<T1, T2>::setX(T1 x) {
    m_x = x;
}

template<typename T1, typename T2> T2 Point<T1, T2>::getY() const {
    return m_y;
}

template<typename T1, typename T2> void Point<T1, T2>::setY(T2 y) {
    m_y = y;
}

int main()
{
    // 与函数模板不同的是,类模板在实例化时必须显式地指明数据类型
    // 编译器不能根据给定的数据推演出数据类型
    Point<int, int> p1(10, 10);
    cout << "x=" << p1.getX() << ", y=" << p1.getY() << endl;

    Point<float, float> p2(12.88, 129.65);
    cout << "x=" << p2.getX() << ", y=" << p2.getY() << endl;

    Point<string, string> p3("E180","N210");
    cout << "x=" << p3.getX() << ", y=" << p3.getY() << endl;

    Point<int, float> p4(4, 129.65);
    cout << "x=" << p4.getX() << ", y=" << p4.getY() << endl;

    Point<string, int> p5("hello,world!", 5);
    cout << "x=" << p5.getX() << ", y=" << p5.getY() << endl;

    //除了对象变量,我们也可以使用对象指针的方式来实例化
    Point<string, int>* p7 = new Point<string, int>("hello,world!", 7);
    // (pointer_name)->(variable_name)
    // The Dot(.) operator is used to normally access members of a structure or union.
    // The Arrow(->) operator exists to access the members of the structure or the unions using pointers
    cout << "x=" << p7->getX() << ", y=" << p7->getY() << endl;
    
    delete p7;

    return 0;
}

可变参模板

一个简单的可变模版参数函数,在声明时使用class… T,在实际使用中用T… args表示可传入多个参数。

template <class... T>
void f(T... args)
{    
    cout << sizeof...(args) << endl; //打印变参的个数
}
 
f();        //0
f(1, 2);    //2
f(1, 2.5, "");    //

基于可变模板参数函数,实现递归函数(如实现sum、max等功能,有或者实现传入多个参数的功能)。以下代码中sum(rest…)函数一开始在多个参数时是执行的函数2,直到参数被不断拆解到最后一个时才执行函数1.

//函数1  递归终止函数
template<typename T>
T sum(T t)
{
    return t;
}
//函数2 持续递归函数
//通过这种写法,每次递归都会拆解一个参数到a1中
template<typename T, typename ... Types>
T sum (T a1, Types ... rest)
{
    return a1 + sum<T>(rest...);
}
sum(1,2,3,4); //10

本节内容参考 https://blog.csdn.net/wmy19890322/article/details/121427697

5、特殊关键字

constexpr

constexpr-》常量表达式,指的就是由多个(≥1)常量组成的表达式。换句话说,如果表达式中的成员都是常量,那么该表达式就是一个常量表达式。这也意味着,常量表达式一旦确定,其值将无法修改。

// 1)
int url[10];//正确
// 2)
int url[6 + 4];//正确
// 3)
int length = 6;
int url[length];//错误,length是变量

explicit

explicit-》阻止对象只有一个参数时的的隐式初始化,用于类内的单参数构造函数前面。由于无参数的构造函数和多参数的构造函数总是显示调用,这种情况在构造函数前加explicit无意义。用于禁止编译器执行非预期(往往也不被期望)的类型转换(限制编译器的智能推断)

#include <iostream>
using namespace std;
class Test1
{
public :
	Test1(int num):n(num){}
private:
	int n;
};
class Test2
{
public :
	explicit Test2(int num):n(num){}
private:
	int n;
};
 
int main()
{
	Test1 t1 = 12;//隐式调用Test1(12),完整等效代码为Test1 t1 = Test1(12);
	Test2 t2(13);
	Test2 t3 = 14;//隐式调用Test1(12),被explicit关键字拦截,然后报错
		
	return 0;
}

virtual

virtual用于修饰父类中的函数(构造函数、static静态函数不能用virtual关键字修饰)。当父类函数A被virtual修饰后,父类指针指向的子类对象(子类重载了函数A),在调用函数A时(因为有virtual修饰)会响应父类的A函数。可以用于复杂继承关系时析构函数的修饰,避免多重继承时构造函数和析构函数的紊乱。可以用于修饰父类的析构函数,避免出现内存泄漏(最好别用父类指针指向子类),详情参考: https://blog.csdn.net/itlilyer/article/details/109274204

noexcept

该关键字告诉编译器,函数中不会发生异常,这有利于编译器对程序做更多的优化。一共有noexcept、noexcept(true)、noexcept(false)、noexcept(expression)四种写法(noexcept 默认表示 noexcept(true))。具体参考https://www.cnblogs.com/sword03/p/10020344.html。
单独使用noexcept|noexcept(true),表示其所限定的swap函数绝对不发生异常。然而,使用方式可以更加灵活,表明在一定条件下不发生异常。

void swap(Type& x, Type& y) throw()   //cpp11之前
    {
        x.swap(y);
    }
void swap(Type& x, Type& y) noexcept  //cpp11
    {
        x.swap(y);
    }

如果函数的返回值仅为true或false,甚至可以将函数体写入noexcept中,具体如下所示。

	bool IgnoreCaseCompare(const std::string& a, const std::string& b)noexcept
	{
		if (a.size() != b.size())
			return false;
		for (size_t i = 0; i < a.size(); i++)
		{
			if (tolower(a[i]) != tolower(b[i]))
				return false;
		}
		return true;
	};