C++关键字

本文深入解析C++中的关键关键字,包括volatile、static、const、四种cast转换和extern的详细用法,以及它们在多线程、变量生命周期、类型转换和外部链接等方面的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文整理了我复习时容易混淆或不易记忆的关键字用法和注意事项。欢迎指正和讨论,谢谢。

目录

一、volatile

 二、static【限定访问域,延长生命周期】

(1) static修饰局部变量(静态局部变量)

(2) static修饰全局变量(静态全局变量)

(3) static修饰函数

(4) static修饰类成员:静态成员变量

(5) static修饰类成员:静态成员函数

三、const(常量的)所修饰的对象或变量不能被改变

1. const 用法

(1)用 const 声明一个变量:带类型的常量

(2)用 const 修饰指针变量:注意区分const修饰的是谁

(3)用 const 修饰函数参数:传递过来的参数在函数内不可以改变

(4) 用 const修饰成员函数:在参数列表后面加const

(5) 用 const修饰对象

(6) 用 const修饰函数返回值

2. 相关辨析

(1)区别以下几种变量?const int a; int const a; const int *a; int *const a;

(2)什么是常引用?

3. const 与 define 与enum

(1)怎样才能建立在整个类中都恒定的常量呢?

(2)使用const代替define的好处

(3)const 与 define 对比

四、四种cast转换

(1)const_cast 用法:const →非const

(2)static_cast 用法:各种隐式转换

(3)dynamic_cast 动态类型转换

(4)reinterpret_cast 几乎什么都可以转

为啥不用C的强制转换?

五、extern

C++中的extern ‘C’的作用

如何引用一个已经定义过的全局变量


一、volatile

volatile(不稳定的)限定一个对象可被外部进程(操作系统、硬件或并发线程等)改变,声明时的语法如下:

int volatile nVint;

定义volatile变量,说明该变量可能会意想不到的被修改,这样编译器就不会去假设这个变量的值,而是每次都重新读取该变量的值,而不是使用寄存器中的备份值。

        1)并行设备的硬件寄存器

        2)中断服务子程序中用到的非自动变量

        3)多线程应用中被多个任务共享的变量

 二、static【限定访问域,延长生命周期】

被修饰的变量存储位置在静态区。生命周期长。

  • C:
    • 隐藏。修饰全局变量和函数,该全局变量和函数只能在该源文件中可见,可以解决命名冲突的问题。
    • 变量初始化为零。
    • 保持局部变量内容的持久性。修饰局部变量,局部变量生命周期和源文件一样长
  • C++:
    • static数据成员是与该类相关联而不与该类对象相关联,一个对象修改了该数据成员的值,其他对象也可以看见。
    • static修饰函数,没有this指针,

【具体】

(1) static修饰局部变量(静态局部变量)

对于静态局部变量,相对于一般局部变量其生命周期长,直到程序运行结束而非函数调用结束,且只在第一次被调用时定义;程序开始时分配空间,结束时释放空间,而不需要在每次它进入和离开作用域时进行创建和销毁;

(2) static修饰全局变量(静态全局变量)

对于静态全局变量,相对于全局变量其可见范围被缩小只能在本文件(声明它的文件)中可见;

(3) static修饰函数

修饰函数时作用和修饰全局变量相同,都是为了限定访问域。只有本文件内的代码才可访问它,它的名字(变量名或函数名)在其它文件中不可见。注意:.h文件中声明static全局函数不能加static,在.cpp文件中声明全局函数需要加static。

(4) static修饰类成员:静态成员变量

静态成员变量和静态成员函数不属于任何一个对象,是所有类实例所共有

  • 也就是说当某个类的实例修改了该静态成员变量,其修改值为该类的其它所有实例所见(多对象数据共享);
  • 静态数据成员必须在类的外部定义,类的静态成员变量必须在声明它的文件范围内进行初始化才能使用,private 类型的也不例外。通过 <classname>::<static member> 来使用。

(5) static修饰类成员:静态成员函数

  • 静态成员函数可以不加类名直接引用静态数据成员,但引用类中说明的非静态成员需要加上对象名。
    • 因为当调用一个对象的非静态成员函数时:系统把对象的起始地址赋给成员函数的this指针。
    • 静态成员函数不属于某一对象,没有this指针,就无法对一个对象的非静态成员默认访问(引用数据成员时不指定对象名)。所以要想引用就要:对象名.非静态成员
  • static的数据记忆性可以满足函数在不同调用期的通信,也可以满足同一个类的多个实例间的通信。未初始化时,static变量默认值为0。
  • 每个对象占用的存储空间 = 对象的非静态数据成员的总和,而成员函数和静态数据成员不占存储空间。

三、const(常量的)所修饰的对象或变量不能被改变

1. const 用法

(1)用 const 声明一个变量:带类型的常量

意味着该变量就是一个带类型的常量,可以代替 #define,且比 #define 多一个类型信息,且它执行内链接,可放在头文件中声明;但在 C 中,其声明则必须放在源文件(即 .C 文件)中,在 C 中 const 声明一个变量,除了不能改变其值外,它仍是一个变量。如:

const double pi(3.14159); 

const double pi = 3.14159;

(2)用 const 修饰指针变量:注意区分const修饰的是谁

  • 只有一个const,如果const位于*左侧,表示指针所指数据是常量,不能通过解引用修改该数据;指针本身是变量,可以指向其他的内存单元。
int a1=3; const int * a4 = &a1; ///const data,non-const pointer
  • 只有一个const,如果const位于*右侧,表示指针本身是常量,不能再指向其他内存地址;指针所指的数据可以通过解引用修改。
int * const a5 = &a1; ///non-const data,const pointer

 

  • 两个const,*左右各一个,表示指针和指针所指数据都不能修改

 

int const * const a6 = &a1; ///const data,const pointer 
const int * const a7 = &a1; ///const data,const pointer

(3)用 const 修饰函数参数:传递过来的参数在函数内不可以改变

void test ModifyConst(const int x)
{    x = 5;   } ///编译出错
  • const 只能修饰输入参数。
  • 如果输入参数采用“指针传递”,那么加const 修饰可以防止意外地改动该指针,起到保护作用。
void StringCopy(char *strDestination, const char *strSource);
  • 其中 strSource 是输入参数(不能改内容),strDestination 是输出参数(可更改)。
  • 给 strSource 加上const修饰后,如果函数体内的语句试图改动 strSource 的内容,编译器将指出错误。
  • 如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数(对于非内部数据类型,效率低),影响不到实参的值,该输入参数本来就无需保护,所以不要加const 修饰。
void Func(const A &a); // const传引用例子。 非内部数据类型的输入参数 
void Func(int x); // 非const传值示例。 对于内部数据类型的输入参数,不需要加const,“值传递”和“引用传递”的效率几乎相当。
  • “引用传递”仅借用一下参数的别名而已,不需要产生临时对象但有可能改变参数a,想防止意外改动,加const修饰即可。
    • 但是要注意:函数究竟是要返回一个对象的拷贝,还是仅仅返回一个别名就行了。这种应用,一般只出现在类的赋值函数中,目的是为了实现链式表达。
      A & operate = (const A & other); // 赋值函数
      A a, b, c; // a, b, c 为A 的对象
      a = b = c; // 正常的链式赋值
      (a = b) = c; // 不正常的链式赋值,但合法

      👆如果将赋值函数的返回值加const 修饰,那么该返回值的内容不允许被改动。上例中,语句 a = b = c 仍然正确,但是语句 (a = b) = c 则是非法的。

(4) 用 const修饰成员函数:在参数列表后面加const

  • const成员函数 不能修改任何的成员变量(mutable修饰的变量除外)
  • const成员函数 不能调用非const成员函数,因为非const成员函数可能会修改成员变量
  • const成员函数 不可以修改对象的数据,不管对象是否具有const性质.
class List {
private:
    int length;
    mutable bool is_valid;
public:
    int GetLength () const;  // 常量成员函数
    bool DeleteNode(const int index);   // 普通成员函数, index在函数内不可修改
    bool CheckList() const;  // 常量成员函数
}
// const成员函数int List::GetLength() const{ 
    DeleteNode(2);       // 错误:不能调用非const成员函数
    return length++;     // 错误:不能修改成员变量(mutable类型除外)
}
bool CheckList() const{
    if(length >= 1) then return is_valid =true;
    else return is_valid = false;   // 正确!因为is_valid为mutable类型,可被常量成员函数更改
}
  • const数据成员的初始化必须在构造函数初始化列表中初始化,而不可以在构造函数函数体内初始化(不能在类声明中初始化const数据成员)

(5) 用 const修饰对象

  • const对象只能调用const成员函数。
  • const对象的成员是不可修改的,然而const对象通过指针维护的对象却是可以修改的.
const List myList;    // 成员变量不能被修改。 
myList.GetLength();   // 正确,GetLength是const常函数,它返回链表长度,的确没有改变属性值的行为,检验通过
myList.DeleteNode(3); // 错误,DeleteLength是非const成员函数, 
        // 👆很有可能改变对象中length(链表长度)这个值,这不符合const对象的规定。

(6) 用 const修饰函数返回值

  • 指针传递
    • 如果给以“指针传递”方式的函数返回值加const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。
      const char * GetString(void); 
      // 如下语句将出现编译错误:
      char *str = GetString(); 
      // 正确的用法是:
      const char *str = GetString();

       

    • 即:如果返回const data,non-const pointer,返回值也必须赋给const data,non-const pointer。因为指针指向的数据是常量不能修改。

      const int * mallocA(){  ///const data,non-const pointer
          int *a = new int(2);
          return a;
      }
      int main()
      {
          const int *p = mallocA();
          ///int *b = mallocA();  ///编译错误
          return 0;
      }
  • 值传递 不需要const

2. 相关辨析

(1)区别以下几种变量?const int a; int const a; const int *a; int *const a;

  • int const a 和 const int a均表示定义 整型常量a 
  • const int *a,其中a为指向int型常量的指针,const在 * 左侧,表示a指向不可变常量。(看成const (*a),对引用加const)
  • int *const a,依旧是指针类型,表示a为指向整型变量常指针。(看成const(a),对指针const)

(2)什么是常引用?

  • 常引用可以理解为常量指针,形式为const typename & refname = varname
  • 常引用下,原变量值不会被别名所修改
  • 原变量的值可以通过原名修改
  • 常引用通常用作只读变量别名或是形参传递。

3. const 与 define 与enum

(1)怎样才能建立在整个类中都恒定的常量呢?

使用类中的enum枚举常量。

  • enum 枚举常量不会占用对象的存储空间,它们在编译时被全部求值。
  • 枚举常量的缺点是:它的隐含数据类型是整数,其最大值有限,且不能表示浮点数(如PI=3.14159)。
    enum em{ SIZE1 = 100, SIZE2 = 200}; // 枚举常量,sizeof(em)是

(2)使用const代替define的好处

          1)类型检查 2)某些集成可以对const的进行调试

(3)const 与 define 对比

 

const

#define

定义常量

定义的常数是变量 有数据类型

定义的只是个常数 不带类型

起作用的阶段

在编译、运行的时候起作用

在编译的预处理阶段起作用

起作用的方式

const有对应的数据类型

在编译阶段会执行类型检查

只是简单的字符串替换,没有类型检查,仅仅是展开。

define只是简单的字符串替换会导致边界效应:

#define N 2+3 //我们预想的N值是5,我们这样使用N 
double a = N/2; //我们预想的a的值是2.5,实际上a的值是3.5

存储方式

分配内存

不分配内存

空间占用

占用数据段空间

给出了对应的内存地址

在程序运行过程中只有一份拷贝

占用代码段空间

给出的是立即数

常量在内存中有若干个拷贝

#define PI 3.14 //预处理后占用代码段空间 
const float PI=3.14; 
//本质上还是一个 float,占用数据段空间

代码调试

可以进行调试

不能进行调试的,因为在预编译阶段就已经替换掉了

是否可以再定义

不能重定义

可以通过#undef取消掉某个符号的定义,再重新定义

特殊功能

不可

可以用来防止头文件重复引用。

//主要把以下代码放在头文件中,可以防止头文件被重复引用
#ifndef xxx //如果没有定义xxx
#define xxx //定义xxx 
  //这里是你的代码 
#endif //结束如果

PS: 头文件被重复引用的弊端:

(1) 有些头文件重复引用 只是增加了编译工作的工作量,不会引起太大的问题,仅仅是编译效率低一些,但是对于大工程而言编译效率低下那将是一件多么痛苦的事情。

(2) 有些头文件重复包含,会引起错误,比如在头文件中定义了全局变量,这种会引起重复定义。

四、四种cast转换

(1)const_cast 用法:const →非const

const_cast<type_id> (expression)

该运算符用来修改类型的 const 或 volatile 属性除了 const 或 volatile 修饰之外, type_id 和 expression 的类型是一样的。常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。

(2)static_cast 用法:各种隐式转换

static_cast < type-id > ( expression )

该运算符把 expression 转换为 type-id 类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:

  • 用于类层次结构中基类和子类之间,指针或引用的转换。进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。

  • 用于基本数据类型之间的转换,如把 int 转换成 char,把 int 转换成 enum。这种转换的安全性也要开发人员来保证。

  • 把空指针转换成目标类型的空指针。

  • 把任何类型的表达式转换成void类。

注意: static_cast 不能转换掉 expression 的 const、volitale、或者 __unaligned 属性。

(3)dynamic_cast 动态类型转换

只能用于含有虚函数的类,类层次间向上or向下转化。只能转指针or引用

允许在运行时刻进行类型转换,从而使程序能够在一个类层次结构安全地转换类型。dynamic_cast 提供了两种转换方式,把基类指针转换成派生类指针,或者把指向基类的左值转换成派生类的引用。

(4)reinterpret_cast 几乎什么都可以转

reinpreter_cast<type-id> (expression)

type-id 必须是一个指针、引用、算术类型、函数指针或者成员指针

它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值)。

为啥不用C的强制转换?

看起来厉害,实际上转化不够明确,不能进行错误检查,容易出错。

五、extern

  • extern(外部的)声明变量或函数为外部链接,默认为可被外部使用,即该变量或函数名在其它文件中可见
  • 被其修饰的变量(外部变量)是静态分配空间的,即程序开始时分配,结束时释放。用其声明的变量或函数,应该在别的文件或同一文件的其它地方定义(实现)。
  • 在 C++ 中,还可用来指定使用另一语言进行链接,这时需要与特定的转换符一起使用。目前仅支持C转换标记,来支持 C 编译器链接。使用这种情况有两种形式:
extern "C" 声明语句
extern "C" { 声明语句块 }

C++中的extern ‘C’的作用

  • extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义
  • extern ‘C’修饰的变量和函数,按照C语言编译器的方式来编译和链接

如何引用一个已经定义过的全局变量

  • 引用头文件的方式:在编译器就可以发现错误
  • extern关键字:在链接期间才报错

参考:菜鸟教程、牛客网C++专题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值