美文网首页already
第15章 源文件与程序 & 软件设计

第15章 源文件与程序 & 软件设计

作者: my_passion | 来源:发表于2022-06-23 15:57 被阅读0次

15.0 Note

    (1) `带 初始化值` 的 `(extern) 声明` 被视为 `定义: extern 被忽略`
    
    (2) 默认初始化
        
        global/namespace scope 的 变量定义 才会...

15.1 分离编译

1 按 逻辑结构(模块) 在 物理结构(文件) 上划分

    (1) 目标: `接口与实现分离` -> 减少依赖 & 节省 编译时间

    (2) 源文件
         |  
         |/
        预处理 
         |
         |  [1] 宏
         |  [2] #include 包含头文件 
         |/
        编译单元: compiler 处理 

15.2 链接

1 链接方式: linker 能 access name 的 scope: 外部/内部/无(linker 看不到) 编译单元

    [1] 外部链接         
        函数 / global 对象          
    
    [2] 内部链接        
        1] static 实体 (对象/函数): 必为 内部链接               
        2] namespace (类) 中 const 对象 / constexpr 对象 / 类型别名
                默认 内部链接, `extern 可改链接方式为 外部链接`
                
    [3] 无链接 
         local non-static 变量名 

2 头文件: 应该包含什么实体(一般原则) & 不能包含什么(否则, 编译报错)?

    #include 机制: #include 源文件 & #inclue 头文件(实现 ODR) 

(1) 应该包含什么

想在 多个编译单元可见(可被 access) 的 ...: 需保持 ODR(单一定义规则: 定义一致性)
而非 static 实体 (对象/函数)

    1) 5组 定义

        类型          struct Point { int x, y; };          
                        enum class Light { red, yellow, green };
                        
        模板              template <typename T> class C { /*  */ };
            
        inline    函数 
        constexpr 函数 
        
        constexpr 对象    constexpr float pi2 = 3.1415926;
        const     对象    const float pi = 3.14;
        
        具名 namespace (定义) & inline namespace

    2) 4组 声明
    
        名字          class Matrix;
        模板          template <typename T> class Z;
        函数          extern int strlen(const char*);
        数据          extern int a;

    3) 4种 预处理
    
        宏        定义         #define 
        包含     指令           #include 
        条件编译 指令             #ifdef __cplusplus 
        注释 
    4)
        类型别名                using value_type = long;
        编译时断言               static_asser(4 <= sizeof(int), "small ints");
        
    note: `条件编译` 不是 编译时 机制, 是 `预处理 / 预编译` 时 机制
    
    Note
        ——————————————————————————————————————————————————————————————————
        inline 函数                                 外部编译单元 是否可调用 
        ——————————————————————————————————————————————————————————————————
        [1] `头文件` 中 `定义` + #include 头文件     是 
        ——————————————————————————————————————————————————————————————————
        [2] `本 编译单元 内 定义` inline 函数             否 
        ——————————————————————————————————————————————————————————————————  

(2) 不能包含什么

    [1] 普通 `函数定义
    [2] 普通 `数据定义`   
    [3] 匿名 namespace 
    [4] `集合` 定义     int tbl[] = {1, 2};
    

3 ODR/单一定义规则

(1) `模板 & inline 函数: 2个定义 可视为 唯一定义, 只要 `不违反 ODR`
                                                            
(2) 违反 ODR
    
    [1] `重定义` 
        
    [2] 2个 编译单元内容同, 但 `含义不同`: 被 `类型别名/宏 改变了含义`
        
        防止违反 ODR: 令 `头文件` 尽可能地 `自包含`    
        
// === [2]
// file1.cpp
    typedef int X;
    struct S {X x; char b; };

// file2.cpp
    typedef char X;
    struct S {X x; char b; };

// === 防止违反ODR
// 1) s.h
    struct S {Point a; char b; };

// 2) file1.cpp
    #define Point int
    #include "s.h"


// 2) file2.cpp
    class Point { /* ... */ };
    #include "s.h"

    |   令 `头文件` 尽可能地 `自包含`:
    |       将 s.h 中 struct S 中 Point 定义 放 s.h 前 
    |       错误就能检测出来 
    |/
    
// 1) s.h
    class Point { /* ... */ };
    struct S {Point a; char b; };

// 2) file1.cpp
    // ...

// 3) file2.cpp
    #include "s.h"

4 C/C++ 标准头文件 关系

    C++ <cX>
        |
        |   一一对应 
        |
    C <X.h>                             

(1) <cstdio>
    
    #ifdef __cplusplus  // 1) 区分 `C++ 代码` 和 `用于 C 编译器 的代码`
        namespace std
        {
            extern "C"  // 2) `链接` 问题 
            {
    #endif
                int printf(const char*, ...);
                // ...
                
    #ifdef __cplusplus
            }
        }
    // ...
    using std::printf;  // 3) `namespace` 问题 
    
    [1] 令 printf 在 `全局 namespace` 中 可用
                    
        有的 compiler 的 <cstdio> 实现中没有用 `using 声明` 声明 printf,  则
        C++ user 程序 #include <cstdio> 后, 要用 std::printf("hello"); 
        而不是 printf("hello"); // error: 无全局 printf
                                        
    [2] std::printf, 仍是那个 C printf()                    
    
(2) <stdio.h>
        
    #include <cstdio>
        
    using std::printf; // C user 程序: 加上肯定不会错, 
                       // 没加 & <cstdio> 实现中没用 `using 声明` 声明 printf => 直接用 printf 会 compile error 

5 链接 C 代码 到 C++ 程序

(1) extern "C": C 表示 `链接规范`

    告诉 C++ compiler `按 指定语言 (C) 方式 (name, 调用习惯 等) 处理` 其中 content, 以使 `linker 按预期 link`
        
(2) `C 和 C++ common 头文件: extern "C" + 条件编译`

    #ifdef __cplusplus
    extern "C" {
    #endif
    
        char* strcpy(char*, const char*);

    #ifdef __cplusplus
    }
    #endif

6 链接 和 函数指针: C & C++ 混合编译

(1) 函数指针声明 & 函数原型声明 的 链接规范 要相相同 -> 再 use mathched 函数调用
    _ _ _ _     
    |       extern "C" int compareC(const void*, const void*); // 1) 函数原型声明
    |
    |   - - int compare(const void*, const void*);
    |   |   
    |   |       
    |   |   typedef int (*FT)(const void*, const void*);
    |   |   void sort(void* p, size_t sz, FT cmp);
    |   |                                     |\
    |   |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
    |   
    |       extern "C" {
    |           typedef int (*CFT)(const void*, const void*);  // 2) 函数指针声明
    |           void sortC(void* p, size_t sz, CFT cmp);
    |                                                |\
    |__ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|  
    |       }
    |       
    |       void f(char* v, int sz)
    |       {
    |           sort(v, sz, &compare);   // C++ 函数指针
    |           
    |- - - - -> sortC(v, sz, &compareC); // C 函数指针         // 3) 函数调用
            }
            
(2) std::function / 带捕获 的 lambda: 无法跨越 语言的障碍

7 全局变量

    1) 问题: 难维护 & 多线程中数据竞争
    
    2) 使用应限制在 `单一源文件`
        
        [1] static (C in practice) 
            
        [2] 放 无名 namespace: 效果 像 `内部链接`

15.3 头文件 使用

1 单头文件 组织

(1) extern 可确保 多个 .cpp #include 1个 .h 时, 不会发生 重定义

(2) compiler & linker 能检测出的错误: 1] 类型不匹配 2] undeclared & 1] undefined 2] redefined

(3) namespace 成员 声明与定义分离: 显式限定 定义 新成员, 还需 (在 namespace 中) add 新声明, 否则 compile error

    namespace N
    {
        class C
        {
        pubic:
            C  f1();
        };
    }
    N::C N::C::f1() { /* ... */ }

2 多头文件 组织

(1) 最小化 相互依赖: DIP/依赖倒置 => 插件式 软件架构: (C 语言实现) 多态

插件式: 无关性, 只要 新插件 handle.cpp 实现了 所要求的函数 f2, 就可以 替换掉 旧插件 handle.cpp

    目标: `func1 调 func2` -> 2 种方法
        
    1) `func1 直接依赖 func2`               

        func1&func2 均放 1.h

                _ _ _ _ 依赖 / 控制流
               |       |
               |       |/
        1.h: func1 + func2 + ...
        |\                  
        |  
        |   依赖             
        |                   
        1.cpp       
        
    2)  DIP & 插件式架构

                    依赖
              _  _ _ _ _ _ _ _ _ _ _
             |                      |
             |                      |/  抽象接口
            1.h: func1 声明       handle.h: pHandle 声明  
             |\                         |\    
        依赖 |                            | 依赖
             |                          |     
           1.cpp                    handle.cpp: 
             func1 定义(高层) - - - - ->f2() 定义 (低层)
                                控制流 pHandle 的 函数指针成员 赋值 为 &f2
    // === 1.h
    #pragma once
    #include "handle.h" // 依赖于 抽象

    void f1();

    // === 1.cpp 
    #include "1.h"

    #include<iostream>
    void f1()
    {
        std::cout << "f1():\n";
        pHandle->f2();
    }

    // === handle.h 
    #pragma once

    // === Hanlde: mem 存 函数指针
    struct Handle
    {
        void (*f2)();
    };

    extern struct Handle* pHandle;

    // === handle.cpp 
    // 插件式: 无关性, 只要 新插件 handle.cpp 实现了 所要求的函数 f2, 就可以 替换掉 旧插件 handle.cpp
    #include "handle.h"

    #include <iostream>
    void f2()
    {
        std::cout << "f2():\n";
    }

    struct Handle handle = { &f2 };
    struct Handle* pHandle = &handle;

    // === main.cpp
    #include "1.h"
    int main()
    {
        f1();
    }

(2) 逻辑模块 - 头文件 - 源文件

    逻辑模块
        |
        | 一一对应: 定义 (逻辑模块) 特性
        |/      
    头文件
        |\ 一一被包含: 描述 (源文件)   接口  => `用户接口`
        |       
        |
    源文件 
        也 #include `other 头文件`: 视为 本源文件的 `实现者接口`
        即, 用 `other 模块 实现 自身接口` 中 `所宣称的 服务` 

(3) 多头文件 优势: 关注点局部化

    [1] 易 find `依赖关系`

    [2] 易 从 局部视角 "由内而外" 维护 & `伸缩` 到大规模( 万/.../亿 行 )程序`
    
    [3] `编译速度 更快`: 可能 大幅提高
(4) 单/多 头文件组织

    1) 避免多头文件 `过度使用` 
    
        逻辑划分尺度应合适, 若 太细, 如 每个结构声明 放 单独的头文件, 则头文件过多 很难管理 
    
    2) 单头文件 和 多头文件 `互补`, 不是互相替代的

3 包含保护

类定义/inline 函数头文件同一编译单元 被 #include 两次 -> compile error

    解决:
    
    (1) 去冗余
        
        1] 减少 编译时间/编译依赖`
        2] 很难用在 大规模程序: `希望 冗余`, 使各 part `独立可理解`
        
    (2) 包含保护: 重复包含头文件 & 条件编译(第1次包含 时才 #include)
        
        过度使用:声明/宏 大量引入 => `不可预知` 的 糟糕方式 
                              
        #ifndef _FILE1_H
        #define _FILE1_H
        
        // ...
        
        #endif 

15.4 初始化和并发 & 程序终止

1 non-local (静态分配)变量

non-local (静态分配)变量 `初始化机制` 就是 `启动 C++ 程序的机制`

(1)初始化顺序

在 `单编译单元` 下 `与定义顺序一致`
在 多编译单元下 undefined => not 安全 => 初始化 `不要依赖 other 编译单元` 对象值

[1] `常量表达式` 初始化: `链接时初始化`   -> 线程安全: 前提 `不能依赖 other 编译单元` 中 对象的值

[2] else               : `运行时初始化`

(2)并发: 多线程下 递增带副作用的表达式 -> not 线程安全 -> 如何避免?

[1] `常量表达式` 初始化 
[2] `无副作用的表达式` 初始化 
[3] `单线程启动阶段` 初始化
[4] `互斥`

(3)替代: 返回引用 to local static 变量

[1] 常量表达式初始化 => 链接时初始化: 线程安全    
[2] `递增`                            : not 线程安全
            
int& use_count()
{
    static int uc = 0;
    return uc;
}

void f()
{
    cout << ++use_count(); // 读取并递增  
}

2 程序 终止

    (1) 6种 方式 
        
        ————————————————————
        从 main() 返回 
        ————————————————————
        调 exit()
        调 quick_exit()
        调 abort()
        ————————————————————
        抛出 未捕获的异常 
        违反 noexcept
        ————————————————————
    
    (2) 调 exit()
        
        其 `caller 函数` 和 caller 函数中的 `local 变量` 
            
            不会执行 各自的 dtor
            
    (3) `离开 context/上下文` 的 最好方式: `抛出异常`
        
        main() 可 捕获 所有异常    
        
    (4) atexit()
        
        在 `程序终止过程中 执行其 arg 所指 func`
            
            arg: 函数指针 - paraType&returnType 均 为 空/void` 
            
        void my_cleanup(){}
        atexit(&my_cleanup); 
ODR.jpg 多头文件 组织 + 正交设计.jpg

相关文章

  • 第15章 源文件与程序 & 软件设计

    15.0 Note 15.1 分离编译 1 按 逻辑结构(模块) 在 物理结构(文件) 上划分 15.2 链接 ...

  • 【IOS开发进阶系列】APP性能优化专题

    1 优化资源文件 在iOS本地资源文件编译后放置与应用程序包(Bundle)文件中即<应用名>.app文件。 NS...

  • BAT面试专题:深入理解JVM——图解JVM调优

    JVM:java虚拟机,java的核心与基础,用来运行java的程序 1、java程序的运行过程 java源文件被...

  • 软件架构设计培训心得

    软件设计培训总结(一) 什么是软件设计 说不上来,反正目前我靠这个养家糊口。 为什么需要软件设计 “杀死一个程序员...

  • Java文件命名规则

    Java文件的命名规则 Java程序源文件的命名必须满足两种规则 1、Java程序源文件的后缀必须为(.java)...

  • OC对象的序列化和反序列化

    "应用程序包"结构: "应用程序包": 这里面存放的是应用程序的源文件,包括资源文件和可执行文件。 NSStri...

  • 简说设计模式之设计模式概述

    一、软件设计模式的概念与意义 1. 软件设计模式的概念 软件设计模式(Software Design Patter...

  • C语言本质

    除了Hello World这种极简单的程序之外,一般的程序都是由多个源文件编译链接而成,这些源文件 的处理步骤通常...

  • 7.Properties子类(Hashtable子类 了解)

    国际化程序特点:同一个程序,根据不同的语音环境选择资源文件,所有的资源文件后缀必须是"*.properties"。...

  • 静态代理

    含义 代理类在程序运行前就已经定义好.java 源文件,其与目标类的关系在程序运行前就已经确立。在程序运行前代理类...

网友评论

    本文标题:第15章 源文件与程序 & 软件设计

    本文链接:https://www.haomeiwen.com/subject/steyvrtx.html