跟我学c++中级篇——注解

214 篇文章 28 订阅

一、注解是什么

其实中这里分析注解的原因是和反射有些关系的,当然实现注解的手段可能有很多种,但一般来说,注解实现都和反射或多或少有着关系。那么什么是注解?注解就是对类或方法等的元数据描述。所以注解就是一种元数据,它是一种对代码的特殊标记。
注解可以有两种方式来应用,一种是在编译期进行扫描;另外一个是在运行期使用反射的方式来获取相关的注解信息。所以从这两点来分析,c++目前来看,只能在编译期进行注解的处理。在运行期利用反射,至少标准目前还不支持,都是一些框架自己实现的。

二、c++11以上的注解

在早期的c++标准中,对注解可以说是没有支持的必要。毕竟玩儿c++的可都不是一般的程序员,但再高大上的东西,总有一天要“飞入寻常百姓家”,而且注解这个东西确实是对开发者有着不小的作用,所以从c++11开始,注解就慢慢上来了。不过在c++中可能不叫注解,叫属性(Attribute)。

Syntax
[[ attribute-list ]]		(since C++11)
[[ using attribute-namespace : attribute-list ]]		(since C++17)
where attribute-list is a comma-separated sequence of zero or more attributes (possibly ending with an ellipsis ... indicating a pack expansion)

identifier	(1)
attribute-namespace :: identifier	(2)
identifier ( argument-list )	(3)
attribute-namespace :: identifier ( argument-list )	(4)
1) Simple attribute, such as [[noreturn]].
2) Attribute with a namespace, such as [[gnu::unused]].
3) Attribute with arguments, such as [[deprecated("because")]].
4) Attribute with both a namespace and an argument list.

其实在早期的开发者中,也可以看到一些编译器厂商自己增加的属性,如GNU 的语言扩展 _attribute_((…)),微软的语言扩展 __declspec() 等,一般情况下,属性可以应用于c++中所有的位置,包括类型、变量、函数、代码块甚至整个单元等等。不过这里强调的注解可能更货币于函数和类型。也就是类似于:

[[attribute]] types/functions

在c++11以后定义了下列几个标准的属性:

C++ 标准仅定义下列属性。

[[noreturn]](C++11 起)	指示函数不返回
[[carries_dependency]](C++11 起)	指示释放消费 std::memory_order 中的依赖链传入和传出该函数。
[[deprecated]](C++14 起)
[[deprecated("原因")]](C++14 起)	指示允许使用声明有此属性的名称或实体,但因 原因 而不鼓励使用。
[[fallthrough]](C++17 起)	指示从前一 case 标号直落是有意的,而在发生直落时给出警告的编译器不应该为此诊断。
[[nodiscard]](C++17 起)
[[nodiscard("原因")]](C++20 起)	鼓励编译器在返回值被舍弃时发布警告。
[[maybe_unused]](C++17 起)	压制编译器在未使用实体上的警告,若存在。
[[likely]](C++20 起)
[[unlikely]](C++20 起)	指示编译器应该针对通过某语句的执行路径比任何其他执行路径更可能或更不可能的情况进行优化。
[[no_unique_address]](C++20 起)	指示非静态数据成员不需要拥有不同于其类的所有其他非静态数据成员的地址。
[[assume]](C++23 起)	指出某个表达式在某个地方始终会求值为 true。
[[optimize_for_synchronized]](TM TS)	指示应该针对来自 synchronized 语句的调用来优化该函数定义

对于支持自定义属性,目前标准还是比较模糊,反正c++有一条,如果不合适,结果就是未知,自己考虑清楚即可。

三、例程

先看几个cppreference上的例程:

[[ noreturn ]] void f() {
  throw "error";
  // OK
}

void q [[ noreturn ]] (int i) {
  // 若以 <= 0 的参数调用则行为未定义
  if (i > 0) {
    throw "positive";
  }
}

// void h() [[noreturn]]; // 错误:属性应用到 h 的函数类型,而非 h 自身

象一些系统调用的API函数,都可以增加这个属性来防止未知情况。

#include <iostream>

[[deprecated]]
void TriassicPeriod() {
    std::clog << "Triassic Period: [251.9 - 208.5] million years ago.\n";
}

[[deprecated("Use NeogenePeriod() instead.")]]
void JurassicPeriod() {
    std::clog << "Jurassic Period: [201.3 - 152.1] million years ago.\n";
}

[[deprecated("Use calcSomethingDifferently(int).")]]    
int calcSomething(int x) {
    return x * 2;
}

int main()
{
    TriassicPeriod();
    JurassicPeriod();
}

再看一个前面曾经提到过的:

#include <iostream>

int f(int i)
{
    if (i < 0) [[unlikely]] {
        return 0;
    }

    return 1;
}

int main()
{
    std::cout << f(-1) << std::endl;

    std::cout << f(1) << std::endl;
}

最后再看一个常用的:

void f(int n)
{
    void g(), h(), i();

    switch (n)
    {
        case 1:
        case 2:
            g();
            [[fallthrough]];
        case 3: // 直落时不警告
            h();
        case 4: // 编译器可在发生直落时警告
            if (n < 3)
            {
                i();
                [[fallthrough]]; // OK
            }
            else
            {
                return;
            }
        case 5:
            while (false)
            {
                [[fallthrough]]; // 非良构:下一语句不是同一迭代的一部分
            }
        case 6:
            [[fallthrough]]; // 非良构:没有后继的 case 或 default 标号
    }
}

其实很多小的技术细节,往往会被开发者忽略,但当某个时刻看到某些人应用的非常巧妙时,又击节叹服。其实这说白了还是对整个体系掌握的不够深入,等深入掌握了,某些细节其实就是习惯的事情了。

四、总结

其实谈注解并没有多少太多的新奇的技术,在其它高级语言,特别是搞Java的同学中,这个太多了,现在几乎是普遍了。所以c++也在跟进,好的东西,其实是普遍受欢迎的,正如美是没有国界的。但c++固有的属性使其的发展不可能一下子就对注解之类的技术支持非常完美。但是只要开了头,后面就会越来越好。
这里把这个注解拿出来,就是和反射在一起分析,让大家有一个更深刻的认识。对开发者来说简单的事情,可能对底层开发支持人员,就是一个新技术的应用。每个次语言标准的进步,其实就是很多基础开发人员的努力,希望有一天,我们也能坐到这个位置,为普通开发者,提供技术支持。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++ 中,我们可以使用串口类来进行串口通信。这里我们将自定义一个串口类,以便更好地理解串口通信的原理和实现。 首先,我们需要引入一些头文件: ```c++ #include <windows.h> #include <iostream> ``` 接着,我们定义一个串口类,其中包含了串口的打开、关闭、读取和写入等操作: ```c++ class SerialPort { public: SerialPort(); ~SerialPort(); bool Open(int portNo, int baudRate); bool Close(); int ReadData(char *buffer, unsigned int nbChar); bool WriteData(char *buffer, unsigned int nbChar); bool IsOpened() const; private: HANDLE m_hComm; bool m_bOpened; }; ``` 其中,m_hComm 是串口的句柄,m_bOpened 表示串口是否打开。 接下来,我们来实现这些操作。 首先是串口的打开操作: ```c++ bool SerialPort::Open(int portNo, int baudRate) { if (m_bOpened) { Close(); } char portName[50]; sprintf_s(portName, "\\\\.\\COM%d", portNo); m_hComm = CreateFile(portName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); if (m_hComm == INVALID_HANDLE_VALUE) { std::cerr << "Failed to open serial port!\n"; return false; } DCB dcb; GetCommState(m_hComm, &dcb); dcb.BaudRate = baudRate; dcb.ByteSize = 8; dcb.Parity = NOPARITY; dcb.StopBits = ONESTOPBIT; if (!SetCommState(m_hComm, &dcb)) { std::cerr << "Failed to set serial port parameters!\n"; CloseHandle(m_hComm); return false; } m_bOpened = true; return true; } ``` 这里我们首先判断串口是否已经打开,如果已经打开则先关闭,然后根据串口号构建串口名称,并使用 CreateFile 函数打开串口。接着,我们设置串口的参数,包括波特率、数据位、校验位和停止位等。如果设置成功,则将 m_bOpened 设置为 true 并返回 true,否则返回 false。 接下来是串口的关闭操作: ```c++ bool SerialPort::Close() { if (!m_bOpened) { return true; } if (CloseHandle(m_hComm)) { m_bOpened = false; return true; } return false; } ``` 这里我们只需要调用 CloseHandle 函数关闭串口,并将 m_bOpened 设置为 false 即可。 接下来是串口的读取操作: ```c++ int SerialPort::ReadData(char *buffer, unsigned int nbChar) { DWORD bytesRead; if (!ReadFile(m_hComm, buffer, nbChar, &bytesRead, nullptr)) { std::cerr << "Failed to read data from serial port!\n"; return -1; } return bytesRead; } ``` 这里我们使用 ReadFile 函数从串口读取数据,并将读取的字节数存储在 bytesRead 变量中,并返回 bytesRead。 最后是串口的写入操作: ```c++ bool SerialPort::WriteData(char *buffer, unsigned int nbChar) { DWORD bytesSent; if (!WriteFile(m_hComm, buffer, nbChar, &bytesSent, nullptr)) { std::cerr << "Failed to write data to serial port!\n"; return false; } return true; } ``` 这里我们使用 WriteFile 函数将数据写入串口,并返回写入是否成功的结果。 最后,我们来实现一个判断串口是否打开的函数: ```c++ bool SerialPort::IsOpened() const { return m_bOpened; } ``` 这里只需要返回 m_bOpened 即可。 至此,我们就完成了一个简单的自定义串口类。可以通过实例化这个类来进行串口通信的操作,具体使用方法可以参考下面的示例代码: ```c++ int main() { SerialPort port; if (!port.Open(1, 9600)) { return -1; } char buffer[1024]; while (true) { int bytesRead = port.ReadData(buffer, sizeof(buffer)); if (bytesRead > 0) { buffer[bytesRead] = '\0'; std::cout << buffer << std::endl; } } port.Close(); return 0; } ``` 在这个示例中,我们首先实例化了一个 SerialPort 类,并通过 Open 函数打开了 COM1 号串口。然后,我们通过 ReadData 函数从串口读取数据,并将读取的数据打印到控制台上。最后,我们通过 Close 函数关闭了串口。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值