• C/C++常用关键字详解


    目录

    1、sizeof

    2、malloc和free、new和delete

    3、const

    3.1、修饰非指针变量

    3.2、修饰指针变量

    3.3、const用在函数中的场景

    4、try、throw和catch

    5、inline

    6、explicit

    7、mutable    

    8、asm

    9、extern和static


           C/C++中有很多常见的关键字,有些关键字使用起来很简单,有些关键字用的较少但很特殊的用途,常见的C/C++关键字如下所示:

    asm

    do

    if

    return

    typedef

    auto

    double

    inline

    short

    typeid

    bool

    dynamic_cast

    int

    signed

    typename

    break

    else

    long

    sizeof

    union

    case

    enum

    mutable

    static

    unsigned

    catch

    explicit

    namespace

    static_cast

    using

    char

    export

    new

    struct

    virtual

    class

    extern

    operator

    switch

    void

    const

    false

    private

    template

    volatile

    const_cast

    float

    protected

    this

    wchar_t

    continue

    for

    public

    throw

    while

    default

    friend

    register

    true

    delete

    goto

    reinterpret_cast

    try

    今天我们就来介绍一下一些常用的、比较有意思的关键字,或者是有特殊用途的关键字,以供大家参考。 

    本文的内容在日常编程中都会用到,在面试时可能也会涉及到,建议大家来详细学习一下,特别是一些刚毕业的朋友们!

    1、sizeof

           sizeof用来计算变量等对象占用的内存大小,以字节为单位。这个关键字大家肯定都用过,但有一点需要注意的是,sizeof是C++中的关键字,不是C运行时库中的函数(这一点估计很多人可能都不太清楚,即使工作多年的人可能也不太晓得),并且sizeof返回的值在编译期间就确定了

    在这里给大家说个小插曲。之前在一个技术群中,听一个在校大学生说,他在上他们专业开设的《C语言程序设计》课程时,他们老师居然说sizeof是函数,作为老师这都搞不清楚,这位同学顿时就不愿意了,不想跟着这位老师学下去了。

    这点确实不够严谨,做学问还是要严谨一点比较好。其实这也是很多高校IT专业普遍存在的问题,很多任课老师缺乏软件开发的项目经验,对很多知识点的理解不够深入,很难做到理论与实际项目相结合。

    2、malloc和free、new和delete

           malloc和free、new和delete是用来对动态堆内存进行管理的,malloc和free是C语言中的关键字(被C++继承了),new和delete则是C++中的关键字。使用malloc和new去动态申请堆内存,使用free和delete去释放堆内存。

           C++程序的大部分数据都是存储在堆上的,即大部分内存使用都是堆内存,必然要使用到new和delete,对堆内存的操作都是使用指针去完成的,指针是C++中的核心类型之一。使用堆内存所引发的一系列的内存问题也是C++中一个重点难点问题。堆内存上的异常问题比栈内存上的异常要难查很多。

            关于C/C++动态内存管理的专题,可以参考我之前写的文章:

    深入详解C/C++动态内存管理https://blog.csdn.net/chenlycly/article/details/125879661文章中系统全面地讲述了动态申请堆内存的相关内容,并融入了多年来的软件项目开发实战的经验总结,感兴趣的朋友,可以去详细看一下。

    3、const

           这个关键字在日常编码中会经常使用,使用的场景也比较多。用const修饰对象时,表示对象是固定的常量,是不可变的。具体的控制行为和修饰对象有直接的关系,当修饰变量时,必须在定义时对变量进程初始化,下面我们就来逐一看一下。

    3.1、修饰非指针变量

           const关键字可以用在类型说明符前,也可以用在类型说明符后,用来标记变量的值是不可改变的,比如:

    1. const int a=10;
    2. int const a=10;

    3.2、修饰指针变量

           当const被用来修饰指针变量时,会涉及到两个概念,一个是常量指针,一个是指针常量,到底属于哪种,取决于const放在*号的前面还是后面。

            如果将const放置在*的前面,如下:

    1. int a = 10;
    2. const int* p = &a;

    那这个指针就叫做常量指针,常量指针是指其指向的内容是常量,只是代表不能通过指针变量去修改指向的内存中的值,如果对上面示例代码的指针p指向的内存中的内容进行修改,比如:

    *p = 11

    编译时会报错:

    error C3892: “p”: 不能给常量赋值。

    此时可以通过其他的途径去修改指针指向的内存中的值,比如直接操作原始变量:

    a = 11

    这个时允许的。

           如果将const放置在*的后面,如下:

    1. int a = 10;
    2. int* const p = &a;

    那这个指针就叫做指针常量,指针变量本身是个常量,指针变量的值是不可改变的,比如再定义一个int型变量b,将b的地址复制给p:

    1. int b = 10;
    2. p = &b;

    这时去编译就会报错的,因为指针p是指针常量,指针变量的值时不可改变的!但可以使用*p修改指针指向的内存中的内容。

    3.3、const用在函数中的场景

    1)修饰函数的形参

           以函数参数为结构体(或者是类)为例,有设备信息结构体TDeviceInfo,如果直接将结构体对象作为函数的形参,如下所示:

    void Func( TDeviceInfo tDevInfo )  // TDeviceInfo是结构体

    这样在调用该函数时,会构造一个临时对象tDevInfo,然后将传进来的结构体参数以值传递的方式设置到临时对象中,然后函数中使用该临时对象访问传进来的值,在函数退出后再将临时结构体对下个析构掉。

           如果结构体比较大,这个临时对象的构造和析构可能会消耗掉一定的时间和cpu时间。所以,我们为了提高代码的执行效率,我们可以直接将参数定义为引用或者指针,这样就不用构造临时对象了。但设置成结构体指针或引用,Func函数内部就可以随意修改传入的指针或引用指向的内存中的值,这是我们不希望看到的,这个时候就可以用上const了:

    void Func( const TDeviceInfo* pDevInfo )

    或者

    void Func( const TDeviceInfo& tDevInfo )

    这样做是为了防止修改指针或引用指向的内存中的内容,即函数Func内部就不能通过传进来的指针和引用,修改对应内存的值了,同时也解决了需要构造临时对象的问题

           对于函数中形参,const用来修饰指针或引用才是有价值的。如果使用如下的代码:

    void Func( const TDeviceInfo tDevInfo )

    只表示临时构造的tDevInfo对象变量值时不可修改的,这样做没多大意义。

    2)修饰函数的返回值

           当用const修饰函数返回值时,那么函数返回值的内容是不可修改的。如果返回值的类型不是指针或引用,可以随意赋值的,不管接受变量是否有const编译都不会报错的。

           但如果返回值是指针或者引用,则只能赋值给const类型的接收变量,否则编译会报错,比如:

    1. // GetString函数的返回值类型是const char*
    2. const char* GetString();
    3. // 调用GetString函数
    4. char* lpszBuf = GetString()

    会报这样的错误:

    error C2440: “初始化”: 无法从“const char *”转换为“char *”,

    正确的做法是:

    const char* lpszBuf = GetString();

           如果调用函数返回值为指针或者引用,那么其返回的被调用函数所在类的内部的变量内存地址,返回出去只是给外部读取的,外部不能修改的,所以加上const就可以实现这一目的。

    3)修饰C++类的成员函数
           使用const来修饰C++类的成员函数时,该成员函数不能修改类中成员变量的值,比如如下的C++类中定义了一个GetLenth成员函数,函数结尾加上了const关键字:

    1. class ClassA
    2. {
    3. public:
    4.     ClassA();
    5.     ~ClassA();
    6. public:
    7.     int GetLength() const;
    8. private:
    9.     int m_nLength;
    10. };

    那么,GetLength函数中不能成员变量m_nLength,否则编译器会报错。

    在成员函数中添加const来限制该成员函数不要去修改类中成员变量的值,而后面讲到的mutable关键字就是为了突破这个限制,如果成员变量前面添加了mutable修饰,则const成员函数中时可以修改该变量值的。关于mutable关键字后面我们再细说。

    4、try、throw和catch

           在C++函数中检测到异常时,可以使用throw抛出异常值,抛出的可以是一个int等基本数据类型变量,也可以是一个C++类对象。在函数外部我们可以使用try...catch去捕获异常,针对不同的异常值做处理,也可以只捕获异常,不做响应的处理。

           比如以前我们讲过,通过new去申请动态内存时可能会失败,new内部可能会抛出异常,抛出的是一个bad_alloc类对象,然后我们使用try...catch捕获到这个类对象,把类对象的申请内存失败的原因信息打印出来,如下所示:

    1. #include
    2. using namespace std;
    3. int main(){
    4. char *p;
    5. int i = 0;
    6. try
    7. {
    8. do{
    9. p = new char[10*1024*1024];
    10. i++;
    11. Sleep(5);
    12. }
    13. while(p);
    14. }
    15. catch(const std::exception& e)
    16. {
    17. std::cout << e.what() << "\n"
    18. << "分配了" << i*10 << "M" << std::endl;
    19. }
    20. return 0;
    21. }

           还有我们在调用Windows COM组件去实现某一个功能时,COM组件内部可能会抛出异常,如果不捕获不处理异常,则直接会导致软件崩溃。所以我们在使用COM组件时要小心,可以使用try...catch捕获异常,这里有两个作用,一是程序不会因为一个操作崩溃了,二是我们可以在捕获到异常时去释放资源并弹出一个提示框,提示某操作失败了。示例代码如下:

    1. EmOfficeToXpsErr ExcelToXps( CUIString strSrcFilePath, CUIString strXpsFilePath )
    2. {
    3. COleVariant covFalse( (VARIANT_BOOL)FALSE, VT_BOOL );
    4. COleVariant covTrue( (VARIANT_BOOL)TRUE, VT_BOOL );
    5. COleVariant covOne( (short)1 );
    6. COleVariant covOptional( (long)DISP_E_PARAMNOTFOUND, VT_ERROR );
    7. COleVariant covXPSWriter( PRINTER_NAME );
    8. CExcelApplication excelApp;
    9. CWorkbooks workbooks;
    10. CWorkbook workbook;
    11. CWorksheets workSheets;
    12. if ( excelApp.CreateDispatch( _T("Excel.Application") ) == FALSE )// 启动MS Excel
    13. {
    14. if ( excelApp.CreateDispatch( _T("KET.Application") ) == FALSE )// 启动WPS Excel
    15. {
    16. return OTX_START_OFFICE_FAILED;
    17. }
    18. }
    19. // 此处使用try...catch
    20. try
    21. {
    22. excelApp.put_DisplayAlerts( FALSE );// 关闭office提示框
    23. workbooks = excelApp.get_Workbooks();
    24. // 打开Excel文件
    25. workbook = workbooks.Open( strSrcFilePath, covOptional, covOptional, covOptional, covOptional,
    26. covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional,
    27. covOptional, covOptional, covOptional );
    28. long nSheetCount = 0, nSheetIndex = 0, nSheetPrint = 0;
    29. workSheets = workbook.get_Worksheets();
    30. nSheetCount = workSheets.get_Count(); // 获取当前打开的Excel中Sheet数
    31. // 调整每个Sheet为一页宽多页高
    32. for ( nSheetIndex = 0; nSheetIndex < nSheetCount; nSheetIndex++ )
    33. {
    34. CWorksheet workSheet = workSheets.get_Item( COleVariant( (long)(nSheetIndex + 1) ) );
    35. 不处理隐藏的工作表,否则Worksheet打印时会发生崩溃(wordsheet打印时才需要设定)
    36. //if (WorkSheet.get_Visible() != -1)//0:xlSheetHidden; 2:xlSheetVeryHidden ; -1:xlSheetVisible
    37. //{
    38. // WorkSheet.ReleaseDispatch();
    39. // continue;
    40. //}
    41. CPageSetup pageSetup = workSheet.get_PageSetup();// 获取页面设置对象
    42. workSheet.Activate();
    43. // 设置打印网格线
    44. pageSetup.put_PrintGridlines( TRUE );
    45. // 设置打印顺序
    46. // 1:Process down the rows before processing across pages or page fields to the right.
    47. // 2: Process across pages or page fields to the right before moving down the rows.
    48. pageSetup.put_Order( 1 );
    49. // 设置打印纸张方向
    50. // 1: Portrait mode
    51. // 2:Landscape mode
    52. pageSetup.put_Orientation( 2 );
    53. // 设置打印纸张
    54. // 9: A4; 8: A3
    55. pageSetup.put_PaperSize( 8 );
    56. // 获取缩放比为100%时的水平分页符个数
    57. pageSetup.put_Zoom( COleVariant( (short)100 ) );
    58. CHPageBreaks hPageBreaks = workSheet.get_HPageBreaks();
    59. long lHPageNum = 0;
    60. lHPageNum = hPageBreaks.get_Count();
    61. // 设置一页宽多页高
    62. // 必须先将Zoom设为false,PagesWide、PagesTall的设置才会生效
    63. pageSetup.put_Zoom( covFalse ); // covFalse指定为VT_BOOL
    64. pageSetup.put_FitToPagesWide( covOne );
    65. pageSetup.put_FitToPagesTall( COleVariant(lHPageNum+1) );
    66. //nSheetPrint++;
    67. //strFilename.Format(_T("%s_%d%s"),strFilePath, nSheetPrint, _T(".xps"));
    68. //excelWorkSheet._PrintOut(covOptional, covOptional, covOptional, covOptional, covXPSWriter, covOptional, covOptional, COleVariant(strFilename));
    69. hPageBreaks.ReleaseDispatch();
    70. pageSetup.ReleaseDispatch();
    71. workSheet.ReleaseDispatch();
    72. }
    73. // 将文档打印成xps
    74. // PrintToFile设置为true,避免相邻Sheet打印质量不同时提示输入xps文件名
    75. workbook._PrintOut( covOptional, covOptional, covOptional, covOptional, covXPSWriter, covTrue, covOptional, COleVariant( strXpsFilePath ) );
    76. workSheets.ReleaseDispatch();
    77. // 关闭文件,SaveChanges设置为false,避免弹出Excel窗口
    78. workbook.Close( covFalse, covOptional, covOptional );
    79. if ( workbooks.get_Count() == 0 )// 打开文档数为0时才结束进程
    80. {
    81. excelApp.Quit();
    82. }
    83. workbook.ReleaseDispatch();
    84. workbooks.ReleaseDispatch();
    85. excelApp.ReleaseDispatch();
    86. // 判断xps文件是否存在
    87. // 打印过程中点击打印进度提示框的取消按钮,导致无法生成xps文件
    88. if ( PathFileExists( strXpsFilePath ) )
    89. {
    90. return OTX_SUCCESS;
    91. }
    92. else
    93. {
    94. return OTX_COM_EXCEPTION;
    95. }
    96. }
    97. catch(...)
    98. {
    99. // 捕获到异常处理,可以给用户弹出提示框
    100. excelApp.Quit();
    101. workbook.ReleaseDispatch();
    102. workbooks.ReleaseDispatch();
    103. excelApp.ReleaseDispatch();
    104. return OTX_COM_EXCEPTION;
    105. }

           try...catch是C++中的异常捕获机制,C语言中也有一套异常捕获的结构__try...__except:

    1. __try
    2. {
    3. // guarded code
    4. }
    5. __except ( expression )
    6. {
    7. // exception handler code
    8. }

    我们在调用API函数HtmlHelp打开.chm帮助文档时,如果指定路径中的.chm文件不存在(可能是路径不对,也可能是文件被删除了),HtmlHelp函数内部会抛出异常,软件会发生崩溃。后来我们直接添加__try...__except去捕获异常,对调用HtmlHelp的代码添加保护,保证即使.chm文件不存在,也不能让软件发生崩溃,相关代码如下:

    1. bool OpenChmHelpFile( LPCTSTR lpStrPath )
    2. {
    3. HWND hHelpWnd = NULL;
    4. __try
    5. {
    6. hHelpWnd = HtmlHelp( NULL, lpStrPath, HH_DISPLAY_TOPIC, NULL );
    7. }
    8. __except( EXCEPTION_EXECUTE_HANDLER )
    9. {
    10. hHelpWnd = NULL;
    11. }
    12. if ( NULL == hHelpWnd )
    13. {
    14. WriteLog( _T("[OpenChmHelpFile] HtmlHelp execute failed, path [%s]!"), lpStrPath );
    15. return false;
    16. }
    17. return true;
    18. }

           在上述代码中,在__except分支条件中直接设置EXCEPTION_EXECUTE_HANDLER,表示此处我们认领并处理这个异常,这样就不会因为异常未得到处理导致软件崩溃了。我们可以在打开失败时弹出一个提示,提示用户打开失败了。

    5、inline

           inline是用来修饰函数的,以inline修饰的函数叫做内联函数,比如:

    1. inline void * __cdecl memchrInternal(const void *buf, int chr, size_t cnt)
    2. {
    3. #ifdef _X86_
    4. void *pRet = NULL;
    5. _asm {
    6. cld // make sure we get the direction right
    7. mov ecx, cnt // num of bytes to scan
    8. mov edi, buf // pointer byte stream
    9. mov eax, chr // byte to scan for
    10. repne scasb // look for the byte in the byte stream
    11. jnz exit_memchr // Z flag set if byte found
    12. dec edi // scasb always increments edi even when it
    13. // finds the required byte
    14. mov pRet, edi
    15. exit_memchr:
    16. }
    17. return pRet;
    18. #else
    19. while ( cnt && (*(unsigned char *)buf != (unsigned char)chr) ) {
    20. buf = (unsigned char *)buf + 1;
    21. cnt--;
    22. }
    23. return(cnt ? (void *)buf : NULL);
    24. #endif
    25. }

    编译时C++编译器会在调用内联函数的地方展开,就像展开宏一样,没有函数压栈的开销,内联函数提升程序运行的效率。

           因为所有调用内联函数的地方都会被内联函数的函数体实现代码替换掉,这样就会增大编译出来的二进制文件的大小。inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。

    inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。

    6、explicit

           explicit关键字用来修饰C++类的构造函数。在C++中,如果一个类有只有一个参数的构造函数,C++允许一种特殊的声明类变量的方式。在这种情况下,可以直接将一个对应于构造函数参数类型的数据直接赋值给类变量,编译器在编译时会自动进行类型转换,将对应于构造函数参数类型的数据转换为类的对象。如果在构造函数前加上explicit修饰词,则会禁止这种自动转换。比如如下的圆信息类:

    1. class Circle
    2. {
    3. public:
    4.     Circle(double r) : R(r) {}
    5. private:
    6.     double R;
    7. };
    8. // 1、使用类定义一个对象
    9. Circle A = 1.23;

    上述代码中的:

    Circle A = 1.23;

    编译器编译时会转换为:

    1. // 编译器再编译时会转换为:
    2. Circle tmp(1.23); // 先构造
    3. Circle A(tmp); //再拷贝构造

           如果在构造函数前加上explicit关键字,如下:

    1. class Circle
    2. {
    3. public:
    4.     explicit Circle(double r) : R(r) {}
    5. private:
    6.     double R;
    7. };

    则上面的代码中的Circle A = 1.23;,在编译时就会报错,则只能这样来使用:

    Circle A(1.23);

    7、mutable    

           C++关键字mutable是为了突破const关键字的限制而存在的,当一个变量被mutable修饰,那么它被定义为永远可变。比如:

    1. class Circle
    2. {
    3. public:
    4.     Circle(double r) : R(r) {}
    5. private:
    6.     mutable double R; // 使用mutable关键字
    7. };

    这会和const关键字的性质起到冲突吗?const关键字修饰函数的意思是这个函数不修改任何对象内部状态。而mutable关键字修饰数据成员变量表示这个成员变量不属于对象内部状态。

           什么时候使用mutable关键字?我们为了使得类的成员函数不去修改类的数据成员变量,用const修饰成员函数,但是,这会将所有的成员变量的修改一棍子打死全部不可在此函数中修改。我们是为了保护一些特定的数据成员变量,如银行类中的用户存款值,用户信息等。但某些信息就没那么重要了,这些数据字段就用mutable来修饰。

    8、asm

          asm关键字被用来在C/C++源代码中切入一段汇编代码的,比如:

    1. inline void * __cdecl memchrInternal(const void *buf, int chr, size_t cnt)
    2. {
    3. #ifdef _X86_
    4. void *pRet = NULL;
    5. _asm {
    6. cld // make sure we get the direction right
    7. mov ecx, cnt // num of bytes to scan
    8. mov edi, buf // pointer byte stream
    9. mov eax, chr // byte to scan for
    10. repne scasb // look for the byte in the byte stream
    11. jnz exit_memchr // Z flag set if byte found
    12. dec edi // scasb always increments edi even when it
    13. // finds the required byte
    14. mov pRet, edi
    15. exit_memchr:
    16. }
    17. return pRet;
    18. #else
    19. while ( cnt && (*(unsigned char *)buf != (unsigned char)chr) ) {
    20. buf = (unsigned char *)buf + 1;
    21. cnt--;
    22. }
    23. return(cnt ? (void *)buf : NULL);
    24. #endif
    25. }

    一般我们会在一些对执行效率要求比较高的代码中嵌入汇编代码,提高代码的执行效率,汇编代码的执行效率是最高的。比如我们在处理音视频编解码的算法代码中,时常会嵌入一些汇编代码,以提高代码的运行速度。

    有人可能会问,为啥直接在源代码中嵌入汇编代码后执行效率会比较高呢?
    经过IDE编译出来的二进制文件中也都是汇编指令,你人为的添加一段汇编代码,都是汇编代码,为啥会有执行速度上的差别呢?因为源代码经过编译器编译生成的汇编代码在实现上可能不是最优的,这要依赖编译器,而我们人为地去嵌入汇编代码,可以直接操纵寄存器和汇编指令,编译器不会再去做处理(不再依赖编译器),保证汇编代码是最优的。

    9、extern和static

           static和exrern是C语言中的关键字,C++语言中在处理C++类时做了一定的延伸。extern用来声明外部全局变量,static可以用来声明变量、全局函数及C++类的静态函数。关于extern和static两关键字的详细说明,可以参见之前写的一篇文章:

    一文带你彻底搞懂C/C++编程中static与extern两关键字的使用https://blog.csdn.net/chenlycly/article/details/125703772文章通过项目实践详细讲述了这两个关键字的使用。

  • 相关阅读:
    GraphQL入门之使用ApolloServer构建GraphQL服务
    linux的三剑客
    T-SNE最简单的示例,自制非抄袭
    The 2022 ICPC Asia Regionals Online Contest (II)
    【Leetcode刷题笔记05】242.有效的字母异位词 349. 两个数组的交集 202. 快乐数 1. 两数之和
    MySQL产生死锁原因
    MAUI Blazor (Windows) App 动态设置窗口标题
    双十一购物狂欢节准备好买什么了吗?双十一这些好物不能错过
    中国全自动榨汁机行业市场深度分析及发展规划咨询综合研究报告
    华为OD机考算法题:服务器广播
  • 原文地址:https://blog.csdn.net/chenlycly/article/details/126148360