欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Windows下sentry接入C/C++程序的詳細過程

 更新時間:2022年09月29日 09:40:06   作者:小?琛  
sentry作為一個開源的軟件,發(fā)展至今,已經非常成熟。它支持的平臺眾多,甚至于針對不同的工作者(后臺、前端、客戶端)都有相應的內容,這篇文章主要介紹了Windows下sentry接入C/C++程序,需要的朋友可以參考下

sentry簡介

首先,如果你是為工作而做,公司一定有自己的sentry賬號,去找他們申請權限吧;如果是學習或自己用,先去這里申請一個賬號sentry.io。記得,綁定的郵箱很關鍵,之后的所有崩潰、消息、異常sentry都會發(fā)送郵件給你

sentry的作用

為大家通俗的總結:針對開發(fā)中的項目,即使我們做足了一切的排查,可仍然有出現crash(崩潰)的可能,這是我們必須承認的;或者,有些時候我們使用try catch,對于某些catch我們很希望知道它的觸發(fā)率是什么。因此,sentry的本質就是做這樣的工作:為我們的工程上一層保險,讓我們開發(fā)人員能隨時知道自己維護的項目是怎樣的狀態(tài)

舉個例子:讓大家感受一下

例如:這里的項目,我們可以清楚的看到,崩潰觸發(fā)率等很多我們關心的內容

下方的圖片,我們甚至可以清楚的定位到,本次crash發(fā)生在線程29428,SentryTest.exe中main函數的71行

這里只舉了兩個最最基本的例子,但已經能看到sentry的強大。

sentry-native是什么

sentry作為一個開源的軟件,發(fā)展至今,已經非常成熟。它支持的平臺眾多,甚至于針對不同的工作者(后臺、前端、客戶端)都有相應的內容。而我們本次討論的是針對C/C++的版本
sentry官方GitHub鏈接

基于C/C++的native版,SDK編譯

有一點我在這里吐槽一下:sentry的官方只給了源碼和編譯方法,為什么不送佛送到西,給咱們把SDK編譯好?。?!(如果你熟悉vckpg,其實vckpg可以直接集成sentry)

sentry-native C/C++版本源碼

1.截至到我寫這篇博客,更新到0.5版本,使用可以將其對于的zip下載即可

2.開始編譯工作
我只能說,這里的編譯應該是他們的疏忽,官方給的流程中有一些問題,我將整個流程進行匯總,并將有問題的地方在后面標注(希望在你使用的時候,這些問題已經被修復)

  • sentry的SDK構建借助的是CMake,在你下載的工程目錄下,打開cmd

如果你的開發(fā)環(huán)境是 macOS ,流程如下

# Configure the CMake build into the `build` directory with crashpad (the default
# backend on macOS, thus optional to specify). Specifying `RelWithDebInfo` as the 
# `CMAKE_BUILD_TYPE` is also optional because it is the default in sentry-native
# for all generators supporting it.
cmake -B build -D SENTRY_BACKEND=crashpad -D CMAKE_BUILD_TYPE=RelWithDebInfo
# build the project
cmake --build build --parallel
# install the resulting artifacts into a specific prefix
cmake --install build --prefix install
# which will result in the following (on macOS):
exa --tree install --level 2
install
├── bin
│  └── crashpad_handler
├── include
│  └── sentry.h
└── lib
   ├── cmake
   ├── libsentry.dylib
   └── libsentry.dylib.dSYM

如果你是在Windows下,流程如下

# The msbuild generator ignores the CMAKE_BUILD_TYPE because it contains all 
# build-types. Here we leave out the backend specification and rely on CMake
# selecting crashpad as Windows' default backend.
cmake -B build 
# The actual build step then requires we specify which build-type we want 
# to apply via the `--config` parameter. Please be aware that in msbuild 
# projects, the `--parallel` option has no effect. 
cmake --build build --config RelWithDebInfo
# install the resulting artifacts (again requiring build-type!)
cmake --install build --prefix install --config RelWithDebInfo
# which will result in the following output (ignoring non-essential lines):
tree /f install
├───bin
│       crashpad_handler.exe
│       crashpad_handler.pdb
│       sentry.dll
│       sentry.pdb
│
├───include
│       sentry.h
│
└───lib
    │   sentry.lib

接下來,進行問題的解決(如果沒有問題,忽略這些內容):

0.5版本整個工程編譯都會報警告:提示修改文件為Unicode。這是因為這些文件是他們在Linux下做的,所以文件都是UTF-8無簽名,而Windows下需要UTF-8帶簽名。但文件這么多,如果一個個在visual stdio下改不現實,因此,打開CmakeList,將警告忽略,添加一句代碼:add_definitions(-w)

一定要注意

一定要注意,上圖的例子是我在Windows下做所以寫在這里,如果你是Mac下,就要寫在外面

工程里面給的example部分,竟然有語法錯誤。。。這我真沒想到。解決辦法:自己打開visual stdio解決一下,或者另一個方式,忽略即可,只要正常編譯出來了dll、lib、exe即可。

或者打開build目錄,cmake成功后,會生成解決方案,用visual stdio打開,這里一定要將編譯方法選為下圖這個

接著,對每一個項目(有問題的RUN_TASK略過)進行生成

如果你要編譯x86版本,除了在vs下將所以相關x64的設置都改成x86或win32,也許還會遇到這個錯誤:模塊對于 SAFESEH 映像是不安全的錯誤。解決方法是,對于出問題的模塊,屬性->鏈接器->命令行->添加:/SAFESEH:NO

暫時就這么多錯誤,如果你還有其它的錯誤,百度搜一搜,或問問大佬,別放棄,想想我當時也很難的?。?!

sentry-SDK接入本地工程

接入我們自己的工程

接下來,我們將編譯好的SDK接入工程,注意:我所有的操作都是以Windows下例子,如果你是后端,去看對應的官方文檔吧 sentry入口
ps:SDK,就是我們編譯出來的sentry.dll、sentry.lib、crashpad_handler.exe,sentry.h通俗來講就是可以直接集成到我們工程里的內容。

接下來的操作,熟悉代碼編譯原理和程序運行原理的朋友一定覺得我啰嗦

1.熟悉代碼編譯原理和程序運行原理的朋友一定不陌生,一個接入的SDK想要成功編譯,需要將頭文件引入

2.lib庫是頭文件中那些函數的具體實現,你需要在你的工程中將其鏈接進來,可以使用 #pragma comment(lib, “sentry.lib”)手動鏈接,亦可以在鏈接器中添加(一定要鏈接成功,否則編譯的時候會報錯)

3.sentry.dll是編譯出來的程序正常運行需要的動態(tài)庫,你需要將它和編譯出來的內容放到同級目錄下,否則編譯出來的exe是無法正常運行的。

4.crashpad_handler.exe,這是sentry執(zhí)行崩潰操作的exe,也就是說,我們調用的接口,最終都是它來執(zhí)行操作。因此,想讓sentry正常工作,需要將它和編譯產物放到一起(和sentry.dll一樣)。

關于配置

一些必要信息的初始化

記住一句話,sentry的初始化越早越好,始終記得,你的目的是讓sentry監(jiān)控你的項目狀態(tài)。

這里先上一段代碼吧

#include <sentry.h>

int main(void) {
  sentry_options_t *options = sentry_options_new();
  sentry_options_set_dsn(options, "https://examplePublicKey@o0.ingest.sentry.io/0");
  sentry_options_set_release(options, "my-project-name@2.3.12");
  sentry_options_set_debug(options, 1);
  sentry_options_set_environment(options, "production");
  sentry_options_set_database_path(options, ".native");
  sentry_init(options);

  /* ... */
}

上面的代碼完成了基本的初始化,也就是說sentry已經可以正常工作了,接下來細說

1.初始化用到的對象
sentry_options_t *options = sentry_options_new();這段代碼中options就是后面需要用的對象,或許你可以把它的定義放到類內成員函數處dsn
2.DSN告訴 SDK 將事件發(fā)送到哪里。如果未提供此值,SDK 將嘗試從SENTRY_DSN環(huán)境變量中讀取它。如果該變量也不存在,SDK 將不會發(fā)送任何事件。
關于dsn的查看如下圖,這個dsn是獨有的,也是固定的,因此代碼中可以直接寫死,即調用sentry_options_set_dsn接口,第一個參數就是初始化的對象,第二個參數即dsn地址,是一個字符串

3.Release版本
通常你的產品有多個不同版本,有些時候你可能還需要對它們進行區(qū)分,如下圖,你可以清晰的看到每個版本的表現

使用sentry_options_set_release(參數1,參數2);參數1為初始化對象,參數2為具體版本(一個字符串)

4.觸發(fā)環(huán)境
有些時候,你的產品可能面向多種用戶,例如:product、develop、test。設置環(huán)境去進行區(qū)分
sentry_options_set_environment(參數1,參數2);參數1為初始化對象,參數2為環(huán)境(一個字符串)

5.日志目錄
sentry的運行是會產生目錄的,如果不設置,就會以當前路徑為默認目錄,在一些面向用戶的產品中,這是肯定不允許的,也許你需要將它設置為temp臨時目錄下去
sentry_options_set_database_path(參數1,參數2);參數1為初始化對象,參數2為目錄路徑(字符串,絕對路徑和相對路徑均可)

自定義配置

有些時候,你可能需要一些其它信息來輔助你排查錯誤,或者是處理后續(xù)的事宜,這里列舉幾個,可以選擇性參考

添加一個自定義content字段

例如上圖,這個character字段就是我自己添加的。
下面的函數是我自己封裝的函數,第一個參數為自定義的內容,例如:character,第二個參數為一個map,輸入你希望的信息,例如:uuid:19,name:Mighty Fighter,都是調用了sentry SDK接口。在需要添加信息的地方使用即可

void addContext(const char* contextName, std::unordered_map<char*, char*> textContent)
{
    if (contextName == nullptr || textContent.empty()) {
        return;
    }
    sentry_value_t character = sentry_value_new_object();

    for (auto it = textContent.begin(); it != textContent.end(); it++) {
        sentry_value_set_by_key(character, it->first, sentry_value_new_string(it->second));
    }

    sentry_set_context(contextName, character);
}

豐富本次觸發(fā)的用戶信息
下面的函數是我自定義的,為本次在觸發(fā)添加用戶信息,這里添加了觸發(fā)的ip地址和uuid,當然你也可以自定義其它內容。注意:sentry可以自動拿到ip地址,格式如下面的代碼:sentry_value_new_string(“{{auto}}”)

void addUser(const char* ipAddress, const char* uuid) 
{
    if (uuid == nullptr) {
        return;
    }
    sentry_value_t user = sentry_value_new_object();
    if (ipAddress == nullptr) {
        sentry_value_set_by_key(user, "ip_address", sentry_value_new_string("{{auto}}"));
    } else {
        sentry_value_set_by_key(user, "ip_address", sentry_value_new_string(ipAddress));
    }

    sentry_value_set_by_key(user, "uuid", sentry_value_new_string(uuid));
    LOG_INFO << "[SentryReporter::addUser]: add User: uuid: " << uuid;

    sentry_set_user(user);
}

利用sentry監(jiān)視崩潰

在完成所有的初始化工作后,sentry就可以為你的工程進行監(jiān)控了,在出現crash時,即可向你的客戶端發(fā)送報告。

崩潰回調

sentry提供了崩潰回調函數,什么作用呢?在發(fā)送crash時,sentry會執(zhí)行它的所有工作,攔截+上發(fā),但在這之前,如果你想做一些其它的事情,也是可以的,看如下代碼:

sentry_value_t SentryReporter::onCrashCallback(
    const sentry_ucontext_t* uctx, // Crashed user space context
    sentry_value_t event,
    void* closure                  // User data that provide during configuration
)
{
    const std::wstring uuid = APP->getMainWindow()->getUUID();
    addUser(nullptr, Utils::StringUtils::ws2s(uuid).c_str());

    //if you want to give up this crash,Add a judgment method and make the following if true
    if (GIVE_UP_CRASH) {
        sentry_value_decref(event);
        return sentry_value_new_null();
    }

    return event;
}

//----------------初始化時添加-------------------------
sentry_options_set_on_crash(optioning, onCrashCallback, NULL);

代碼中,實現了一個函數onCrashCallback,并在sentry初始化時進行綁定,這樣做之后,就可以達到上面說的效果。

你可以在崩潰回調函數中做一些操作,例如:添加一些附加信息,又或者調用接口放棄上傳此次崩潰
一定要注意,sentry沒有做線程安全處理,因此這里的崩潰回調函數一定要輕量級,不要做過重的計算操作

手動發(fā)送異常、消息

sentry的另一種用法就是我們手動上發(fā)事件,舉個例子:代碼中try catch,我希望得知某些catch的觸發(fā)率,就可以在對應的catch處進行一個手動的數據上發(fā)。
看如下代碼:

void capturingErrorsWithStacktrace(const char* errorMessage) 
{
    sentry_value_t event = sentry_value_new_event();

    sentry_value_t exc = sentry_value_new_exception("Exception", errorMessage);
    sentry_value_set_stacktrace(exc, NULL, 0);
    sentry_event_add_exception(event, exc);
    LOG_INFO << "[SentryReporter::capturingErrorsWithStacktrace]:sentry sent error,message: " << errorMessage;

    sentry_capture_event(event);
}

void capturingMessages(sentry_level_t level, const char* logger, const char* message) 
{
    if (logger == nullptr || message == nullptr) {
        return;
    }

    sentry_value_t event = sentry_value_new_message_event(level, logger, message);
    LOG_INFO << "[SentryReporter::capturingMessages]:sentry  sent message,level: " 
        << level << "logger: " << logger << "message: " << message;

    sentry_capture_event(event);
}

上面的兩個函數,一個可以手動發(fā)送錯誤、一個可以手動發(fā)送一個自定義消息,這里就不講述了,自己看代碼理解一下。

消息回調

與crash回調一樣,手動發(fā)送的消息也可以有回調函數,這里直接給出實現,原理請看上文:

/*
* This function sends an error or message manually to make a callback
* You can do some operations, such as adding information
*/
sentry_value_t SentryReporter::beforeSendCallback(sentry_value_t event, void* hint, void* closure)
{
    //do something before send
    return event;
}


//---------------------設置發(fā)送消息回調--------------------------
sentry_options_set_before_send(m_pOptions.get(), beforeSendCallback, NULL);

上傳PDB,直接定位崩潰發(fā)生的位置

sentry的另一個強大之處,在于當發(fā)生崩潰時,它不僅可以及時捕捉并上報,還可以配合正確的PDB(Windows下的調試文件),精確的定位到發(fā)生crash的exe或dll,甚至可以定位到具體哪一行代碼

  • 適用于 iOS、iPadOS、tvOS、watchOS 和 macOS 的dSYM 文件
  • 適用于 Linux 和 Android (NDK) 的ELF 符號
  • 適用于 Windows 的PDB 文件
  • 適用于所有平臺的Breakpad 符號
  • WebAssembly 的WASM 文件
  • 適用于 Java 和 Android 的ProGuard 映射

崩潰發(fā)生在線程29428,SentryTest.exe中main函數71行


崩潰發(fā)生在線程29428,SentryTest.exe中main函數71行

流程如下:

1.最直接的方式是借助第三方的工具sentry-cli.exe,下載鏈接,下載下來的內容僅僅一個sentry-cli.exe,在cmd下利用該exe進行操作即可

2.首先,你可能需要先對身份進行配置,cmd下該命令,其中如果你是使用的自己項目組或公司的sentry,需要將https://myserver.invalid/換為自己項目的url,這里放置的url是sentry io的官方環(huán)境

sentry-cli --url https://myserver.invalid/ login

3.上傳調試文件,這里以Windows下的pdb為例子

#首先對調試文件進行檢查,是否格式正確
sentry-cli difutil check SentryTest.pdb

如果出現了類似以下內容,即為格式正確

Debug Info File Check
  Type: elf debug companion
  Contained debug identifiers:
    > 924e148f-3bb7-06a0-74c1-36f42f08b40e (x86_64)
  Contained debug information:
    > symtab, debug
  Usable: yes

接下來,將該文件上傳,其中 < org >要換你的項目所在組織;< project > 要換位你的項目名稱;后面的files則為具體文件,支持絕對路徑和相對路徑,且支持上傳多個文件

sentry-cli upload-dif -o <org> -p <project> files...

出現類似如下內容,則上傳成功

> Found 2 debug information files
> Prepared debug information files for upload
> Uploaded 2 missing debug information files
> File processing complete:

  PENDING 1ddb3423-950a-3646-b17b-d4360e6acfc9 (MyApp; x86_64 executable)
  PENDING 1ddb3423-950a-3646-b17b-d4360e6acfc9 (MyApp; x86_64 debug companion)

又或者,你可以去sentry項目處查看:

博主自己封裝的sentry類

注意:這里的代碼是不能直接用的,因為涉及到了一些信息的提取,需要根據自身項目進行適配,在對sentry有一定了解后,再來看

SentryReporter.h

#pragma once
#include "sentry.h"
#include "stdafx.h"
#include <iostream>
#include <string>
#include <unordered_map>

#ifdef ANDROID_SIX
const std::string SENTRY_DSN = "https://b2a9969e03944fae81a12cf84afa6bd2@sentry.netease.com/81";
#else
const std::string SENTRY_DSN = "https://ca88798ea2404243a3dc5a5e2ebdb670@sentry.netease.com/82";
#endif // ANDROID_SIX

const std::string DATABASE_NAME = ".sentry-native";
const std::string ENVIRONMENT = "production";

const bool GIVE_UP_CRASH = false;

struct SentryInitInfo
{
    std::string dsn;
    std::string databasePath;
    std::string releaseVersion;
    std::string environment;
    std::string nemuDownloadLog;
    float sampleRate;//Range must be within :0-1
    int maxBreadcrumbs;//default:100
    bool attachStacktrace;//default:false
};

class SentryReporter {

public:
    SentryReporter();
    ~SentryReporter();
    void setup();
    static void addContext(const char* contextName, std::unordered_map<char*, char*> textContent);
    static void addUser(const char* ipAddress, const char* uuid);//if ipAddress = nullptr,get from system
    static void capturingErrorsWithStacktrace(const char* errorMessage);
    static void capturingMessages(sentry_level_t level, const char* logger, const char* message);

private:
    void fetchInitInfomation();
    static sentry_value_t onCrashCallback(const sentry_ucontext_t* uctx, sentry_value_t event, void* closure);
    static sentry_value_t beforeSendCallback(sentry_value_t event, void* hint, void* closure);

private:
    shared_ptr<sentry_options_t> m_pOptions;
    SentryInitInfo m_sentryInitInfo;
};

SentryReporter.cpp

#include "SentryReport.h"
#include "utils.h"
#include "VersionNo.h"
#include "MainWindow.h"

sentry_value_t SentryReporter::onCrashCallback(
    const sentry_ucontext_t* uctx, // Crashed user space context
    sentry_value_t event,
    void* closure                  // User data that provide during configuration
)
{
    const std::wstring uuid = APP->getMainWindow()->getUUID();
    addUser(nullptr, Utils::StringUtils::ws2s(uuid).c_str());

    //if you want to give up this crash,Add a judgment method and make the following if true
    if (GIVE_UP_CRASH) {
        sentry_value_decref(event);
        return sentry_value_new_null();
    }

    return event;
}

/*
* This function sends an error or message manually to make a callback
* You can do some operations, such as adding information
*/
sentry_value_t SentryReporter::beforeSendCallback(sentry_value_t event, void* hint, void* closure)
{
    //do something before send
    return event;
}

SentryReporter::SentryReporter()
    :m_pOptions(sentry_options_new())
{
    fetchInitInfomation();
}

SentryReporter::~SentryReporter()
{
    sentry_close();
    m_pOptions = nullptr;
}

void SentryReporter::fetchInitInfomation()
{
    m_sentryInitInfo.dsn = SENTRY_DSN;

    std::wstring databasePath = Utils::pathCombine(DCommon::DSystemUtils::getTempPath(), Utils::StringUtils::s2ws(DATABASE_NAME));
    m_sentryInitInfo.databasePath = Utils::StringUtils::ws2s(databasePath);

    m_sentryInitInfo.releaseVersion = Utils::StringUtils::ws2s(STRVERSION);
    m_sentryInitInfo.environment = ENVIRONMENT;

    std::wstring logFilePath = Utils::getLogFile();
    m_sentryInitInfo.nemuDownloadLog = Utils::StringUtils::ws2s(logFilePath);

    sentry_options_set_auto_session_tracking(m_pOptions.get(), false);
    sentry_options_set_symbolize_stacktraces(m_pOptions.get(), true);

    //use default value
    m_sentryInitInfo.sampleRate = 1;
    m_sentryInitInfo.maxBreadcrumbs = 100;
    m_sentryInitInfo.attachStacktrace = false;
}

void SentryReporter::setup()
{
    sentry_options_set_dsn(m_pOptions.get(), m_sentryInitInfo.dsn.c_str());
    sentry_options_set_database_path(m_pOptions.get(), m_sentryInitInfo.databasePath.c_str());
    sentry_options_set_release(m_pOptions.get(), m_sentryInitInfo.releaseVersion.c_str());
    sentry_options_set_environment(m_pOptions.get(), m_sentryInitInfo.environment.c_str());
    sentry_options_set_traces_sample_rate(m_pOptions.get(), m_sentryInitInfo.sampleRate);
    sentry_options_add_attachment(m_pOptions.get(), m_sentryInitInfo.nemuDownloadLog.c_str());

    sentry_options_set_on_crash(m_pOptions.get(), onCrashCallback, NULL);
    sentry_options_set_before_send(m_pOptions.get(), beforeSendCallback, NULL);

    int ret = sentry_init(m_pOptions.get());
    if (ret != 0) {
        LOG_INFO << "[SentryReporter::setup]: sentry init error";
    }
}

void SentryReporter::addContext(const char* contextName, std::unordered_map<char*, char*> textContent)
{
    if (contextName == nullptr || textContent.empty()) {
        LOG_INFO << "[SentryReporter::addContext]: contextName==nullptr or textContent==nullptr";
        return;
    }
    sentry_value_t character = sentry_value_new_object();

    for (auto it = textContent.begin(); it != textContent.end(); it++) {
        sentry_value_set_by_key(character, it->first, sentry_value_new_string(it->second));
    }

    sentry_set_context(contextName, character);
}

void SentryReporter::addUser(const char* ipAddress, const char* uuid) 
{
    if (uuid == nullptr) {
        LOG_INFO << "[SentryReporter::addUser]: uuid==nullptr";
        return;
    }
    sentry_value_t user = sentry_value_new_object();
    if (ipAddress == nullptr) {
        sentry_value_set_by_key(user, "ip_address", sentry_value_new_string("{{auto}}"));
    } else {
        sentry_value_set_by_key(user, "ip_address", sentry_value_new_string(ipAddress));
    }

    sentry_value_set_by_key(user, "uuid", sentry_value_new_string(uuid));
    LOG_INFO << "[SentryReporter::addUser]: add User: uuid: " << uuid;

    sentry_set_user(user);
}

void SentryReporter::capturingErrorsWithStacktrace(const char* errorMessage) 
{
    sentry_value_t event = sentry_value_new_event();

    sentry_value_t exc = sentry_value_new_exception("Exception", errorMessage);
    sentry_value_set_stacktrace(exc, NULL, 0);
    sentry_event_add_exception(event, exc);
    LOG_INFO << "[SentryReporter::capturingErrorsWithStacktrace]:sentry sent error,message: " << errorMessage;

    sentry_capture_event(event);
}

void SentryReporter::capturingMessages(sentry_level_t level, const char* logger, const char* message) 
{
    if (logger == nullptr || message == nullptr) {
        return;
    }

    sentry_value_t event = sentry_value_new_message_event(level, logger, message);
    LOG_INFO << "[SentryReporter::capturingMessages]:sentry  sent message,level: " 
        << level << "logger: " << logger << "message: " << message;

    sentry_capture_event(event);
}

到此這篇關于Windows——sentry接入C/C++程序的文章就介紹到這了,更多相關Windows C++程序內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • C語言宏定義使用分析

    C語言宏定義使用分析

    在宏定義中,“宏名稱”和“宏字符串”是通過“空格”來區(qū)分的,某些朋友不要混淆了,接下來請祥看本文
    2012-12-12
  • C++精要分析lambda表達式的使用

    C++精要分析lambda表達式的使用

    Lambda表達式是現代C++在C ++ 11和更高版本中的一個新的語法糖 ,在C++11、C++14、C++17和C++20中Lambda表達的內容還在不斷更新。 lambda表達式(也稱為lambda函數)是在調用或作為函數參數傳遞的位置處定義匿名函數對象的便捷方法
    2022-05-05
  • C語言超詳細講解遞歸算法漢諾塔

    C語言超詳細講解遞歸算法漢諾塔

    漢諾塔問題是一個經典的問題。漢諾塔(Hanoi Tower),又稱河內塔,源于印度一個古老傳說。本文將用Java求解這一問題,感興趣的可以學習一下
    2022-05-05
  • C++中雙冒號::用法案例詳解

    C++中雙冒號::用法案例詳解

    這篇文章主要介紹了C++中雙冒號::用法案例詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下
    2021-09-09
  • c語言中main函數用法及知識點總結

    c語言中main函數用法及知識點總結

    在本篇文章里小編給大家分享的是一篇關于c語言中main函數用法及知識點總結內容,有需要的朋友們可以跟著學習參考下。
    2021-10-10
  • C++中using的三種用法舉例詳解

    C++中using的三種用法舉例詳解

    最近在使用中,發(fā)現了一種以前沒學過的using用法,于是在這里,將using的幾種用法總結一下,下面這篇文章主要給大家介紹了關于C++中using的三種用法,需要的朋友可以參考下
    2023-02-02
  • win32使用openfilename瀏覽文件窗口示例

    win32使用openfilename瀏覽文件窗口示例

    這篇文章主要介紹了使用win32 API打開瀏覽文件窗口,使用OPENFILENAME結構體來實現這個功能,需要的朋友可以參考下
    2014-02-02
  • C++中函數重載詳解

    C++中函數重載詳解

    大家好,本篇文章主要講的是C++中函數重載詳解,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下
    2022-02-02
  • 詳解C++基礎——類繼承中方法重載

    詳解C++基礎——類繼承中方法重載

    這篇文章主要介紹了C++基礎——類繼承中方法重載,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-04-04
  • c語言 樹的基礎知識(必看篇)

    c語言 樹的基礎知識(必看篇)

    下面小編就為大家?guī)硪黄猚語言 樹的基礎知識(必看篇)。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05

最新評論