你的第二个函数

创建一个标准函数

main.cpp
#include <iostream>

#include "mclog.h"

// 代码中只需包含 .h 头文件,无需关心 .cpp 源文件
#include "byte.h"

int main(int argc, char **argv)
{
    // 本月奶茶消费记录
    int pay_date_1 = 15 + 21 + 33 + 27;
    int pay_date_7 = 27 + 15 + 11;
    int pay_date_28 = 4 + 4;
    int pay_sum = pay_date_1 + pay_date_7 + pay_date_28;

    // 查看 pay 的二进制存储结构
    // 调用 print_byte_int32 ,该函数需要传入一个 int 参数,返回一个 string 类型
    std::string bit_pay_1 = print_byte_int32(pay_date_1);
    std::string bit_pay_7 = print_byte_int32(pay_date_7);
    std::string bit_pay_28 = print_byte_int32(pay_date_28);
    std::string bit_pay_sum = print_byte_int32(pay_sum);

    // 获取到 print_byte_int32 的返回结果 bit_pay ,打印这个字符串结果
    MCLOG("本月奶茶消费记录");
    MCLOG($(pay_date_1) $(bit_pay_1));
    MCLOG($(pay_date_7) $(bit_pay_7));
    MCLOG($(pay_date_28) $(bit_pay_28));
    MCLOG($(pay_sum) $(bit_pay_sum));
    
    return 0;
}
byte.h
#ifndef BYTE_H
#define BYTE_H

// STL头文件 - 引入字符串功能 - string
#include <string>

// STL头文件 - 处理内存地址与字符 - memcpy
#include <cstring>

// 打印整数二进制函数,调用函数必须要参数,返回值可以选择性接收
// 参数为 int 类型,byte 是参数名称
// 返回值为 string 类型
std::string print_byte_int32(int byte);

// 打印浮点二进制函数
// 同理,参数 int 类型,返回值为 string 类型
std::string print_byte_float32(float byte);

#endif // BYTE_H
byte.cpp
#include "byte.h"

// 函数解析
// std::string 是函数返回值
// print_byte_int32 是函数名称
// int byte 函数参数,int 是类型,byte 是名称
std::string print_byte_int32(int byte)
{
    // 函数作用域开始

    // 准备字符串结果
    std::string ret;

    // 循环的功能是将二进制数据循环推到第一位 bit 上进行判断
    // 1byte 等于 8bit,循环次数为 sizeof(byte) * 8,字节数乘8
    // byte的类型是int,4byte,4*8=32,这个循环会执行32次,
    // 4byte 的 bit 结构 00000000 00000000 00000000 00000000
    //                                                    ^ 这是第一位 bit 位置
    for (int i = 0; i < sizeof(byte) * 8; i++)
    {
        // 这个判断总是在判断第一位 bit 位置
        // 我判断的是 ^ 箭头上面的 bit 是 1 还是 0
        // 二进制存储顺序为 左高右低 ,第一位是又右边的 bit
        // 根据第一位是1,加入1,第一位是0,加入0
        if (byte & 0x01)
        {
            ret.push_back('1');
        }
        else
        {
            ret.push_back('0');
        }

        // 从左往右推 1bit ,第二位会覆盖第一位,第一位会消失
        // >>= 等效于 byte = byte >> 1
        byte >>= 1;
    }

    // 反转字符串顺序
    // 存入 string 的结构是 左低右高,和内存顺序 左高右低 相反,需要反转
    ret = std::string(ret.rbegin(), ret.rend());

    // 返回字符串结果,函数如果存在返回值,你必须调用 return 返回内容
    return ret;

    // 函数作用域结束
}

std::string print_byte_float32(float byte)
{
    // 使用 memcpy 将浮点数转移到整数类型
    int ibyte = 0;
    std::memcpy(&ibyte, &byte, sizeof(ibyte));

    // 调用整数打印二进制函数
    return print_byte_int32(ibyte);
}
打印结果
本月奶茶消费记录 [/home/red/open/github/mcpp/example/05/main.cpp:24]
[pay_date_1: 96] [bit_pay_1: 00000000000000000000000001100000]  [/home/red/open/github/mcpp/example/05/main.cpp:25]
[pay_date_7: 53] [bit_pay_7: 00000000000000000000000000110101]  [/home/red/open/github/mcpp/example/05/main.cpp:26]
[pay_date_28: 8] [bit_pay_28: 00000000000000000000000000001000]  [/home/red/open/github/mcpp/example/05/main.cpp:27]
[pay_sum: 157] [bit_pay_sum: 00000000000000000000000010011101]  [/home/red/open/github/mcpp/example/05/main.cpp:28]

你发现没有,前面一直提到的 print_byte_int32 函数被分成了两个部分,分别放在 byte.h byte.cpp 文件中,这是你学习C++必须要习惯的做法,在接下来我会分析这样做的原因
再此之前,让我们先来了解函数是什么,函数的作用,和函数的用法

什么是函数
// 函数解析
// std::string 是函数返回值
// print_byte_int32 是函数名称
// int byte 函数参数,int 是类型,byte 是名称
std::string print_byte_int32(int byte)
{
    // 函数作用域开始

    // 函数作用域结束
}

在C++中,函数是十分常见的,从 main 函数开始,我们就已经接触了第一个函数,函数是无处不见的,函数也十分关键,我们需要创建一个自己的函数
函数由函数名,参数,返回值构成,我们调用时需要使用函数名和传入参数是必须的,返回值是选择性接收的
函数通常有返回值,你需调用 return 返回相同类型的数据,注意是相同类型,如果你不需要返回值,可以设置为 void 这样你不需要调用 return
{ } 是作用域,在这个作用域内的才是函数的内容,所以请注意你的代码编写位置,作用域内才是函数部分

函数的意义

一段代码为什么要从main函数移动到其他函数里面,这里就要提到重复性了
重复是提取到函数中的关键因素,当你的代码出现重复内容的时候,你就开始要考虑是否需要将它们移动到独立的函数中了,当你的代码已经重复了三次,那最好马上将它们移动到函数中
消除重复是 编程规范 的重要一步
独立功能也是提取到函数中的关键因素,如果你发现代码并没有更多重复,但它是一个独立的功能,那你应该要考虑移动到独立功能函数中,因为独立功能是可能被重复调用的,即使最终只需要调用一次,这个独立功能函数也起到了功能划分的作用
print_byte_int32 函数是一个又意义的参考,它是被多次重复调用的用于显示二进制数的,同时它的功能也是独立的,它同时满足了两种条件,它独立于 本月奶茶消费记录 的价格统计运算,它只负责进行将内存转为二进制字符串,所以即使只调用一次, print_byte_int32 也应该提取成函数

函数分离
// 完整函数 - 在 main.cpp 声明并实现
std::string print_byte_int32(int byte)
{
}
// 分离函数 - 声明在 byte.h 中
std::string print_byte_int32(int byte);

// 分离函数 - 定义在 byte.cpp 中
std::string print_byte_int32(int byte)
{
}

print_byte_int32 函数在上一篇文件中已经出现,但我并没有深入的分析这个函数内容,因为认为它是不完整的,也是不标准的函数,它没有进行 函数分离
函数分离是C++的标准作法,它会一个完整的函数拆分成 声明\定义 两个部分,声明在 .h 头文件中,声明只负责告诉你如果调用它,定义在 .cpp 源文件中,定义是实现这个函数的功能,通常讲的实现函数代码就是说要定义这个函数
区分 声明\定义 ,简而言之就是,声明没有具体功能代码,定义存在具体功能代码
你会发现如果你只在main函数中编写 print_byte_int32 ,它与函数分离的 .cpp 部分是完全一样的,看起来 .h 的声明部分完全是多余的,为什么要分离呢

为什么要分离

为什么要分离呢,这是个好问题,我也不喜欢分离代码,但是在C++中规定,一个函数可以被多次声明,但只能被一次定义
如果你在 .cpp 文件中编写 print_byte_int32 的完整函数,包括在 main.cpp 内,你就不能在其他 .cpp 文件中引入这个函数,如果将完整函数编写在 .h 文件中,你将遇到符号链接重定义的问题,你直接会编译错误
所以想要在其他文件中正确引用 print_byte_int32 函数,就必须将它的声明写在 .h 而实现写在 .cpp 文件,这是不可避免的事情,至少在C++11版本你必须接受这种写法
函数分离是 编程规范 的重要一步

注意重定义问题

前面见到函数分离的事情,如果你编译是没有出现任何问题,但是在运行时却存在错误,那你要注意是否实现了函数分离,你要把代码实现写到源文件中,头文件是不能编写具体功能的
你还要注意是否已经实现了这个函数,因为只声明函数,但不定义,也就是没有具体实现,是不会编译错误的,但运行时不存在这个函数的具体实现就会报错

简单的函数功能分析

我们已经使用了 print_byte_int32 这个函数很多回,但是一直没有解释这个函数的功能,现在让我们简单的认识这个函数,请你先看一下 print_byte_int32 的完整代码,这样会让你更容易理解我说的话
print_byte_int32 函数的返回值是 std::string ,这个类型在 string 头文件中声明,它用于存储字符串
然后进入到 for 循环,这个功能是在它自己的 { } 作用域内,循环运行代码,直到条件结束
在 for 循环中,存在 if else 判断语句分析,它会判断条件是否满足,来执行对于代码,它同样拥有自己的 { } 作用域,不同分支的功能需要编写在不同作用域内
循环内出现 >>= 语句,它会将二进制数据进行整体移动,超出边界的数据会直接消失,我们需要让所有的二进制都出现在第一位,并记录到字符串
然后反转字符串,后返回结果,完成整个函数 print_byte_int32 功能,函数退出
{ } 作用域出现了很多次,在函数\循环\判断中都会出现,作用域用于标记一段代码的开始与结束,如函数一般被限制在一个区域内

字符串类型

string 与 const char * 类型的功能类似,都是用于存储字符串,很多教程可能会将 const char * 放入基本类型中讲解,但是我在基本类型中并没有提到了 const char * 类型,因为 const char * 本身并不是数据类型,而是一种数据结构,而且我认为现代C++中完全可以不使用 const char * 类型的字符串,string 完全可以取代它
但是 const char * 你还是需要了解,它只是一种连续的 char 数组,并在结尾处采用了 ‘\n’ 进行判断是否结束的数据结构,更多细节你需要自行了解

如何编译

CMakeLists.txt
cmake_minimum_required(VERSION 4.0)
project(mcpp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_BUILD_TYPE "Debug")

# 设置源文件
if(ALL OR DIR05)
    set(SRC_FILES
        05/main.cpp
    )
endif()

# 添加新增源文件
list(APPEND SRC_FILES
    src/byte.cpp
)

# 包含头文件目录
include_directories(${CMAKE_SOURCE_DIR}/common)

add_executable(${CMAKE_PROJECT_NAME} ${SRC_FILES})
项目结构
.
├── 05
│   └── main.cpp
├── CMakeLists.txt
├── common
│   ├── byte.h
│   └── mclog.h
├── run.sh
└── src
    └── byte.cpp
添加新增文件
# 添加新增源文件
list(APPEND SRC_FILES
    src/byte.cpp
)

# 包含头文件目录
include_directories(${CMAKE_SOURCE_DIR}/common)

我们添加了 byte.h byte.cpp 两个文件,直接编译会报错,因为在 main.cpp 中我们引用了 byte 的功能却没有说明 byte 在那里,上面的代码会分别告诉编译器头文件和源文件分别在那里,通常编译文件中,头文件会指向一个文件夹,源文件会指定具体文件

快速创建
Alt + Q

通常我们会先创建 byte.h byte.cpp 然后在头文件位置编写声明,然后在 VSCode 中点击函数,可以用快捷键自动生成定义

函数导航
Ctrl + 鼠标左键

函数的导航很重要,你可以使用快捷键快速导航到函数的 声明\定义 中来了解函数的具体功能,当然如果有注释,鼠标悬浮在函数上可以看到注释内容

项目路径

https://github.com/HellowAmy/mcpp.git
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐