主頁 > 後端開發 > C++ 測驗框架 GoogleTest 初學者入門篇 丙

C++ 測驗框架 GoogleTest 初學者入門篇 丙

2023-04-16 07:23:25 後端開發

theme: channing-cyan

*以下內容為本人的學習筆記,如需要轉載,請宣告原文鏈接 微信公眾號「ENG八戒」https://mp.weixin.qq.com/s/RIztusI3uKRnoHVf0sloeg

開發者雖然主要負責工程里的開發任務,但是每個開發完畢的功能都是需要開發者自測通過的,所以經常會聽到開發者提起單元測驗的話題,那么今天我就帶大伙一起來看看大名鼎鼎的谷歌 C++ 測驗框架 GoogleTest,

本文上接《C++ 測驗框架 GoogleTest 初學者入門篇 乙》,歡迎關注公眾號【ENG八戒】查看更多精彩內容,


斷言

什么是斷言?斷言是用來對運算式執行比較的代碼塊,呼叫時類似函式,當運算式一致時,斷言回傳成功,否則失敗,

googletest 的斷言是一組宏定義,分為 ASSERT_* 和 EXPECT_* 兩種,

比如

ASSERT_EQ(1, 2);

EXPECT_EQ(1, 2);

上面用到的兩個斷言都是比較輸入的資料是否相等,主要區別是,ASSERT_* 在失敗時終止程式運行,EXPECT_* 在失敗時不會終止程式運行,但是都會回傳錯誤資訊,因而測驗使用 EXPECT_* 可以發現更多的問題而不會打斷測驗流程,

那么 ASSERT_* 斷言失敗時,跟在其后的陳述句會被忽略執行,如果其中包含對資源的釋放,那么就有會出現資源泄漏的問題,斷言失敗報錯資訊會附帶有堆檢查錯誤,這時出現的資源泄漏問題,真的有必要修復碼?看具體情況而定,

另外,googletest 在斷言失敗后除了可以回傳標準錯誤資訊,還可以附帶回傳自定義錯誤資訊,使用運算子 << 添加自定義錯誤資訊,

ASSERT_EQ(1, 2) << "1 is not equal to 2";

EXPECT_EQ(1, 2) << "1 is not equal to 2";

任何可以傳遞給 ostream 的資料都可以作為自定義錯誤資訊傳遞給斷言,比如 C 字串、string物件,

那么,測驗的基本手段就是利用斷言,除了判斷型的斷言之外,googletest 還提供了其它型別的斷言用于協助測驗,比如顯式成功或失敗、布爾型別斷言、字串比較斷言等,詳情可以前往官網查看手冊,

https://google.github.io/googletest/reference/assertions.html

如何測驗

前面提到在 googletest 中,測驗的范圍分為測驗套件和單個測驗,測驗程式可以包含多個測驗套件,一個測驗套件可以包含多個測驗,

簡單的測驗一般推薦使用 TEST 宏來定義單個測驗,

一般的使用方式如下

TEST(test_suite_name, test_name) {
  // test body
}

test_suite_name 是測驗套件名,test_name 是單個測驗的名稱,書寫時都應該符合 C++ 的識別符號規范,而且不能包含有下劃線_,更詳細的命名規范可以查看下面的鏈接

https://google.github.io/styleguide/cppguide.html#Function_Names

那么 TEST 宏到底代表著什么?一起來看看 TEST 宏定義的源代碼

#define GTEST_STRINGIFY_HELPER_(name, ...) #name
#define GTEST_STRINGIFY_(...) GTEST_STRINGIFY_HELPER_(__VA_ARGS__, )

#define GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \
  test_suite_name##_##test_name##_Test

#define GTEST_TEST_(test_suite_name, test_name, parent_class, parent_id)       \
  static_assert(sizeof(GTEST_STRINGIFY_(test_suite_name)) > 1,                 \
                "test_suite_name must not be empty");                          \
  static_assert(sizeof(GTEST_STRINGIFY_(test_name)) > 1,                       \
                "test_name must not be empty");                                \
  class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                     \
      : public parent_class {                                                  \
   public:                                                                     \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() = default;            \
    ~GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() override = default;  \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
    (const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) = delete;     \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
        const GTEST_TEST_CLASS_NAME_(test_suite_name,                          \
                                     test_name) &) = delete; /* NOLINT */      \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
    (GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &&) noexcept = delete; \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
        GTEST_TEST_CLASS_NAME_(test_suite_name,                                \
                               test_name) &&) noexcept = delete; /* NOLINT */  \
                                                                               \
   private:                                                                    \
    void TestBody() override;                                                  \
    static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;      \
  };                                                                           \
                                                                               \
  ::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_suite_name,           \
                                                    test_name)::test_info_ =   \
      ::testing::internal::MakeAndRegisterTestInfo(                            \
          #test_suite_name, #test_name, nullptr, nullptr,                      \
          ::testing::internal::CodeLocation(__FILE__, __LINE__), (parent_id),  \
          ::testing::internal::SuiteApiResolver<                               \
              parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__),          \
          ::testing::internal::SuiteApiResolver<                               \
              parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__),       \
          new ::testing::internal::TestFactoryImpl<GTEST_TEST_CLASS_NAME_(     \
              test_suite_name, test_name)>);                                   \
  void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody()

#define GTEST_TEST(test_suite_name, test_name)             \
  GTEST_TEST_(test_suite_name, test_name, ::testing::Test, \
              ::testing::internal::GetTestTypeId())

#define TEST(test_suite_name, test_name) GTEST_TEST(test_suite_name, test_name)

這么多預定義處理,不妨嘗試代入上面的一般使用方式,然后展開一下,展開如下

static_assert(sizeof("test_suite_name") > 1,
              "test_suite_name must not be empty");
static_assert(sizeof("test_name") > 1,
              "test_name must not be empty");
			  
class test_suite_name_test_name_Test : public ::testing::Test {
  public:
  test_suite_name_test_name_Test() = default;
  ~test_suite_name_test_name_Test() override = default;
  test_suite_name_test_name_Test(const test_suite_name_test_name_Test &) = delete;
  test_suite_name_test_name_Test & operator=(
      const test_suite_name_test_name_Test &) = delete; /* NOLINT */
  test_suite_name_test_name_Test
  (test_suite_name_test_name_Test &&) noexcept = delete;
  test_suite_name_test_name_Test & operator=(
      test_suite_name_test_name_Test &&) noexcept = delete; /* NOLINT */

  private:
  void TestBody() override;
  static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;
};

::testing::TestInfo* const test_suite_name_test_name_Test::test_info_ =
    ::testing::internal::MakeAndRegisterTestInfo(
        "test_suite_name", "test_name", nullptr, nullptr,
        ::testing::internal::CodeLocation(__FILE__, __LINE__),
        ::testing::internal::GetTestTypeId(),
        ::testing::internal::SuiteApiResolver<
            parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__),
        ::testing::internal::SuiteApiResolver<
            parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__),
        new ::testing::internal::TestFactoryImpl<test_suite_name_test_name_Test>);
		
void test_suite_name_test_name_Test::TestBody() {
  // test body
}

從展開后的代碼,可以看到有一堆代碼,最開始有兩個斷言 static_assert 用來判斷輸入的測驗套件名和測驗名長度是否大于1,所以要求 TEST 宏定義輸入的測驗套件名和測驗名都不能為空,

然后基于 ::testing::Test 派生了一個類,類名是測驗套件名和測驗名串接后再在末尾加上 _Test,類內宣告重寫 TestBody() 方法,

TEST 宏定義后面的 {} 用于定義派生類的成員方法 TestBody() 的函式體,內部填寫標準 C++ 的有效陳述句作為測驗主體,當然也包含呼叫 googletest 提供的模塊內容,注意這個代碼塊是沒有回傳值的,代碼塊執行的斷言失敗時,或者代碼崩潰,則測驗 test_name 失敗,否則成功,

再來看個例子

int square(const int a)
{
  // ...
}

TEST(SquareTest, PositiveNos) { 
    ASSERT_EQ(0, square(0));
    ASSERT_EQ(36, square(6));
    ASSERT_EQ(324, square(18));
}
 
TEST(SquareTest, NegativeNos) {
    ASSERT_EQ(1, square(-1));
    ASSERT_EQ(100, square(-10));
}

上面定義了兩個測驗 PositiveNos 和 NegativeNos,都屬于測驗套件 SquareTest,

googletest 在設計時就指定通過測驗套件來匯總測驗結果,所以驗證同一個邏輯功能的測驗應該定義在同一個測驗套件內,

測驗夾具

在 googletest 里什么是測驗夾具?

測驗夾具這個概念是為了解決當你的同一個邏輯功能測驗里,有多個測驗共用測驗資料或者配置的問題,

需要用到測驗夾具的測驗一般推薦使用 TEST_F 宏來定義單個測驗,

一般的使用方式如下

TEST_F(FixtureTest, test_name) {
  // test body
}

不過,TEST_F 宏的第一個輸入引數不僅僅是測驗套件名稱,同時也是測驗夾具類名,這個測驗夾具類需要自己基于類 ::testing::Test 派生實作,

class FixtureTest : public testing::Test {
protected:
void SetUp() override { ... }
void TearDown() override { ... }
// custom data
};

共用的測驗資料或者配置就在這個派生類里添加即可,SetUp() 用于初始化資料和配置,TearDown() 用于卸載配置,

那么 TEST_F 宏到底代表著什么,和 TEST 宏的區別在哪?一起來看看 TEST_F 宏定義的源代碼

#define GTEST_STRINGIFY_HELPER_(name, ...) #name
#define GTEST_STRINGIFY_(...) GTEST_STRINGIFY_HELPER_(__VA_ARGS__, )

#define GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \
  test_suite_name##_##test_name##_Test

#define GTEST_TEST_(test_suite_name, test_name, parent_class, parent_id)       \
  static_assert(sizeof(GTEST_STRINGIFY_(test_suite_name)) > 1,                 \
                "test_suite_name must not be empty");                          \
  static_assert(sizeof(GTEST_STRINGIFY_(test_name)) > 1,                       \
                "test_name must not be empty");                                \
  class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                     \
      : public parent_class {                                                  \
   public:                                                                     \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() = default;            \
    ~GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() override = default;  \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
    (const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) = delete;     \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
        const GTEST_TEST_CLASS_NAME_(test_suite_name,                          \
                                     test_name) &) = delete; /* NOLINT */      \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
    (GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &&) noexcept = delete; \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
        GTEST_TEST_CLASS_NAME_(test_suite_name,                                \
                               test_name) &&) noexcept = delete; /* NOLINT */  \
                                                                               \
   private:                                                                    \
    void TestBody() override;                                                  \
    static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;      \
  };                                                                           \
                                                                               \
  ::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_suite_name,           \
                                                    test_name)::test_info_ =   \
      ::testing::internal::MakeAndRegisterTestInfo(                            \
          #test_suite_name, #test_name, nullptr, nullptr,                      \
          ::testing::internal::CodeLocation(__FILE__, __LINE__), (parent_id),  \
          ::testing::internal::SuiteApiResolver<                               \
              parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__),          \
          ::testing::internal::SuiteApiResolver<                               \
              parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__),       \
          new ::testing::internal::TestFactoryImpl<GTEST_TEST_CLASS_NAME_(     \
              test_suite_name, test_name)>);                                   \
  void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody()

#define GTEST_TEST_F(test_fixture, test_name)        \
  GTEST_TEST_(test_fixture, test_name, test_fixture, \
              ::testing::internal::GetTypeId<test_fixture>())

#define TEST_F(test_fixture, test_name) GTEST_TEST_F(test_fixture, test_name)

這么多預定義處理,手癢代入一般的使用方式然后展開一下,展開如下

static_assert(sizeof("FixtureTest") > 1,
              "FixtureTest must not be empty");
static_assert(sizeof("test_name") > 1,
              "test_name must not be empty");
class FixtureTest_test_name_Test : public FixtureTest {
  public:
  FixtureTest_test_name_Test() = default;
  ~FixtureTest_test_name_Test() override = default;
  FixtureTest_test_name_Test(const FixtureTest_test_name_Test &) = delete;
  FixtureTest_test_name_Test & operator=(
      const FixtureTest_test_name_Test &) = delete; /* NOLINT */
  FixtureTest_test_name_Test
  (FixtureTest_test_name_Test &&) noexcept = delete;
  FixtureTest_test_name_Test & operator=(
      FixtureTest_test_name_Test &&) noexcept = delete; /* NOLINT */
  
  private:
  void TestBody() override;
  static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;
};

::testing::TestInfo* const FixtureTest_test_name_Test::test_info_ =
    ::testing::internal::MakeAndRegisterTestInfo(
        #FixtureTest, #test_name, nullptr, nullptr,
        ::testing::internal::CodeLocation(__FILE__, __LINE__),
        ::testing::internal::GetTypeId<FixtureTest>(),
        ::testing::internal::SuiteApiResolver<
            FixtureTest>::GetSetUpCaseOrSuite(__FILE__, __LINE__),
        ::testing::internal::SuiteApiResolver<
            FixtureTest>::GetTearDownCaseOrSuite(__FILE__, __LINE__),
        new ::testing::internal::TestFactoryImpl<FixtureTest_test_name_Test>);
void FixtureTest_test_name_Test::TestBody() {
  // test body
}

從展開后的代碼來看,TEST_F 和 TEST 實作基本類似,那么使用時要遵循的規則也是一樣的,除了需要傳入自定義的基于 ::testing::Test 派生類,并且測驗套件名就是測驗夾具類名,

舉個例子,有個模板類 Queue 的邏輯功能需要測驗,它實作了 FIFO 的資料佇列管理,

template <typename E>  // E 是元素型別
class Queue {
 public:
  Queue();
  void Enqueue(const E& element); // 資料入隊
  E* Dequeue();  // 資料出隊,如果佇列為空則回傳 NULL
  size_t size() const;  // 佇列資料長度
  ...
};

然后需要基于 ::testing::Test 派生一個測驗夾具類 QueueTest

class QueueTest : public ::testing::Test {
 protected:
  void SetUp() override {
     q1_.Enqueue(1);
     q2_.Enqueue(2);
     q2_.Enqueue(3);
  }

  // void TearDown() override {}

  Queue<int> q0_;
  Queue<int> q1_;
  Queue<int> q2_;
};

夾具類 QueueTest 內定義了三個佇列資料物件,SetUp() 內對資料物件初始化,q0_ 保持為空,q1_ 入隊一個資料,q2_ 入隊兩個資料,

為什么不實作 TearDown() 呢?TearDown() 本來的設計意圖是卸載配置,不是剛好可以用來清理資料嗎?是的,的確可以,不過這里有個更好的選擇,就是使用類解構式來對佇列清空,這里有個建議就是,能用解構式處理的,盡量用解構式替代 TearDown(),因為用解構式可以確保被呼叫而且呼叫的順序不會亂,但不是說所有情況都建議用解構式替代 TearDown(),這里不展開了,

接著呼叫 TEST_F 定義兩個測驗,基于測驗夾具類 QueueTest,測驗套件名也是 QueueTest,兩個測驗名分別為 IsEmptyInitially 和 DequeueWorks,

TEST_F(QueueTest, IsEmptyInitially) {
  EXPECT_EQ(q0_.size(), 0);
}

TEST_F(QueueTest, DequeueWorks) {
  int* n = q0_.Dequeue();
  EXPECT_EQ(n, nullptr);

  n = q1_.Dequeue();
  ASSERT_NE(n, nullptr);
  EXPECT_EQ(*n, 1);
  EXPECT_EQ(q1_.size(), 0);
  delete n;

  n = q2_.Dequeue();
  ASSERT_NE(n, nullptr);
  EXPECT_EQ(*n, 2);
  EXPECT_EQ(q2_.size(), 1);
  delete n;
}

上面的這兩個測驗定義,都會創建 QueueTest 類物件,分別創建而且不共用,所以資料不會相互影響,

第一個測驗 IsEmptyInitially,googletest 框架會先創建 QueueTest 類物件 obj,呼叫 SetUp() 初始化資料和配置,執行測驗,這里只執行了一個 EXPECT_EQ 斷言,EXPECT_* 型別的斷言失敗后會回傳失敗資訊,不會終止測驗程式,繼續下一步測驗,然后呼叫 TearDown() 清理,最后執行物件 obj 的解構式釋放資源并退出當前測驗,

第二個測驗 DequeueWorks,執行流程與上一個類似,其中測驗內容包含有 ASSERT_* 類別的斷言,這種斷言在失敗后除了會回傳失敗資訊,還會終止測驗程式,如果斷言失敗之后的測驗已沒有意義,那么適合使用 ASSERT_* 類別的斷言,

測驗呼叫程序

其它 C++ 測驗框架在測驗開始前,需要你把測驗排列出來,但是 googletest 不需要這么麻煩, 在 googletest 框架中,定義好測驗后,只需要在 main 部分執行如下代碼即可,

int main(int argc, char **argv)
{
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

InitGoogleTest() 可以對程式的輸入命令執行決議,基于這點可以通過命令列的方式控制測驗框架的運行,

繼續以上面的代碼為例,大致流程如下

  1. InitGoogleTest() 初始化測驗框架,
  2. RUN_ALL_TESTS() 啟動測驗,
  3. 查找測驗套件內的測驗,
  4. 保存配置標志,
  5. 創建 QueueTest 實體,
  6. 呼叫 QueueTest 實體的 SetUp() 初始化資料配置,
  7. 執行測驗,
  8. 呼叫 QueueTest 實體的 TearDown() 卸載資料配置,
  9. 恢復配置標志,
  10. 重復第 3 步,直到所有測驗執行完畢,

RUN_ALL_TESTS() 回傳 0 表示成功,否則失敗,只能在主執行緒里呼叫 RUN_ALL_TESTS(),

在一般的測驗里,如果在測驗運行之前不需要做一些自定義的事情,而且這些事情無法在測驗夾具和測驗套件的框架中表達時,main 函式這部分其實都一樣,那么 googletest 就在庫 gtest_main 里提供了一個很方便的入口點,也就是幫你提前寫好了 main 函式,你可以省去這部分,編譯的時候記得鏈接庫 gtest_main 即可,


好了,這個系列的文章就寫到這里啦,

學習可以等,時間不等人!

關注我,帶你學習編程領域更多核心技能!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/550189.html

標籤:其他

上一篇:人工智能中的頂會

下一篇:一天吃透作業系統八股文

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more