C++ 修饰符使用总结

本文详细总结了C++中的修饰符,包括基础类型修饰符(如signed、unsigned)、限定修饰符(const、volatile、restrict)、类访问修饰符(如public、private、protected)、存储类修饰符(如auto、register、static、extern、mutable、thread_local)。重点讨论了const的不变性、volatile在多线程中的应用、以及存储类修饰符如何影响变量的范围和生命周期。

C++ 修饰符使用总结

基础类型修饰符

C++ 允许在 char、int 和 double 等基础数据类型前放置修饰符。基本类型常用的修饰符有:

  • signed

  • unsigned

  • long

  • short

修饰符 signed、unsigned、long 和 short 可应用于整型,signedunsigned 可应用于字符型,long 可应用于双精度型。

修饰符 signedunsigned 也可以作为 longshort 修饰符的前缀。例如:unsigned long int

C++ 允许使用速记符号来声明无符号短整数无符号长整数。您可以不写 int,只写单词 unsigned、shortunsigned、long,int 是隐含的。例如,下面的两个语句都声明了无符号整型变量。

unsigned x;
// 等价于
unsigned int y;

C++ 解释有符号整数和无符号整数修饰符之间的差别:

#include <iostream>
using namespace std;

int main()
{
   short int i;           // 有符号短整数
   short unsigned int j;  // 无符号短整数
   j = 50000;
   i = j;
   cout << i << " " << j;
   return 0;
}

运行结果

-15536 50000

对于无符号化为有符号的位数运算,采取 N − 2 n N-2^n N2n 的计算方法,n 取决于定义的数据类型 int、short、char、long int 等等,N 为无符号数的数值,例如文中的 N=50000,short 为 16 位,计算方法为 50000 − 2 1 6 50000-2^16 50000216 得到 -15536。

限定修饰符

const 类型

const 类型的对象在程序执行期间不能被修改改变。c++中用const定义了一个常量后,将其写入符号表(symbol table),这使得它成为一个编译期间的常量,没有给变量分配内存,也没有了存储与读内存的操作,使得它的效率也很高。

volatile 类型

修饰符 volatile 告诉编译器不需要优化volatile声明的变量,**让程序可以直接从内存中读取变量。**对于一般的变量编译器会对变量进行优化,将内存中的变量值放在寄存器中以加快读写效率。

当要读取 volatile 声明变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚读取过数据,也不会从寄存器中取值,而是从内存中读取数据。 volatile 可以保证对特殊地址的稳定访问。

注意,在 VC 6 中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。

下面通过插入汇编代码,测试有无 volatile 关键字,对程序最终代码的影响,输入下面的代码:

#include <stdio.h>
void main()
{
    int i = 10;
    int a = i;
    printf("i = %d", a);
 
    // 下面汇编语句的作用就是改变内存中 i 的值
    // 但是又不让编译器知道
    __asm {
        mov dword ptr [ebp-4], 20h
    }
    int b = i;
    printf("i = %d", b);
}

然后,在 Debug 版本模式运行程序,输出结果如下:

i = 10
i = 32

在 Release 版本模式运行程序,输出结果如下:

i = 10
i = 10

输出的结果明显表明,Release 模式下,编译器对代码进行了优化,第二次没有输出正确的 i 值。下面,我们把 i 的声明加上 volatile 关键字,看看有什么变化:

#include <stdio.h>
void main()
{
    volatile int i = 10;
    int a = i;

    printf("i = %d", a);
    __asm {
        mov dword ptr [ebp-4], 20h
    }
    int b = i;
    printf("i = %d", b);
}

分别在 Debug 和 Release 版本运行程序,输出都是:

i = 10
i = 32

一般说来,volatile用在如下的几个地方:

  • 中断服务程序中修改的供其它程序检测的变量需要加 volatile;
  • 多任务环境下各任务间共享的标志应该加 volatile;
  • 存储器映射的硬件寄存器通常也要加 volatile 说明,因为每次对它的读写都可能由不同意义;
volatile 指针

和 const 修饰词类似,const 有常量指针和指针常量的说法,volatile 也有相应的概念:

指针常量:修饰由指针指向的对象、数据是 const 或 volatile 的:

const char* cpch;
volatile char* vpch;

注意:对于 VC,这个特性实现在 VC 8 之后才是安全的。

常量指针:指针自身的值—一个代表地址的整数变量,是 const 或 volatile 的:

char* const pchc;
char* volatile pchv;

注意:

  • 可以把一个非volatile int赋给volatile int,但是不能把非volatile对象赋给一个volatile对象。
  • 除了基本类型外,对用户定义类型也可以用volatile类型进行修饰。
  • C++中一个有volatile标识符的类只能访问它接口的子集,一个由类的实现者控制的子集。用户只能用const_cast来获得对类型接口的完全访问。此外,volatile向const一样会从类传递到它的成员。
多线程下的volatile

有些变量是用 volatile 关键字声明的。**当两个线程都要用到某一个变量且该变量的值会被改变时,应该用 volatile 声明,该关键字的作用是防止优化编译器把变量从内存装入 CPU 寄存器中。**保持数据的一致性,案例:

volatile  BOOL  bStop  =  FALSE;

(1) 在一个线程中:

while(  !bStop  )  {  
    // ...  
}  
bStop  =  FALSE;  
return;    

(2) 在另外一个线程中,要终止上面的线程循环:

bStop  =  TRUE;  
while(  bStop  );  //等待上面的线程终止,如果bStop不使用volatile申明,那么这个循环将是一个死循环,因为bStop已经读取到了寄存器中,寄存器中bStop的值永远不会变成FALSE,加上volatile,程序在执行时,每次均从内存中读出bStop的值,就不会死循环了。

这个关键字是用来设定某个对象的存储位置在内存中,而不是寄存器中。因为一般的对象编译器可能会将其的拷贝放在寄存器中用以加快指令的执行速度,例如下段代码中:

// ...  
int  nMyCounter  =  0;  
for(;  nMyCounter<100;nMyCounter++)  
{  
	// ...  
}  
// ...

在此段代码中,nMyCounter 的拷贝可能存放到某个寄存器中(循环中,对 nMyCounter 的测试及操作总是对此寄存器中的值进行),但是另外又有段代码执行了这样的操作:nMyCounter -= 1; 这个操作中,对 nMyCounter 的改变是对内存中的 nMyCounter 进行操作,于是出现了这样一个现象:nMyCounter 的改变不同步。

restrict 类型

restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。主要用来修饰指针指向的内存不能被别的指针引用。

只能通过这个指针或基于这个指针的其他指针进行操作,即限制访问用restrict限制的指针指向的对象只能通过这个指针访问,这对编译器的优 化很有好处。

类访问修饰符

数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。类成员的访问限制是通过在类主体内部对各个区域标记 public、private、protected 来指定的。

class Base {
   public:
  // 公有成员
 
   protected:
  // 受保护成员
 
   private:
  // 私有成员
};
  • 公有(public)成员: 在程序中类的外部是可访问的。您可以不使用任何成员函数来设置和获取公有变量的值。
  • 私有(private)成员: 私有变量或私有函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。默认情况下,类的所有成员都是私有的。
  • 保护(protected)成员: 保护变量或保护函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的。

explicit 修饰符

C++提供了关键字explicit,只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式)。

C++中, 一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数), 承担了两个角色。 1 是个构造器 ,2 是个默认且隐含的类型转换操作符。

所以, 有时候在我们写下如 AAA = XXX, 这样的代码, 且恰好XXX的类型正好是AAA单参数构造器的参数类型, 这时候编译器就自动调用这个构造器, 创建一个AAA的对象。

这样看起来好象很酷, 很方便。 但在某些情况下(见下面权威的例子), 却违背了我们(程序员)的本意。 这时候就要在这个构造器前面加上explicit修饰, 指定这个构造器只能被明确的调用/使用, 不能作为类型转换操作符被隐含的使用。

class Test1
{
    public:
    Test1(int n)
    {
        num=n;
    }//普通构造函数
    private:
    int num;
};
class Test2
{
    public:
    explicit Test2(int n)
    {
        num=n;
    }//explicit(显式)构造函数
    private:
    int num;
};

int main()
{
    Test1 t1=12;//隐式调用其构造函数,成功
    Test2 t2=12;//编译错误,不能隐式调用其构造函数
    Test2 t2(12);//显式调用成功
    return 0;
}

普通构造函数能够被隐式调用。而explicit构造函数只能被显式调用。

存储类修饰符

**存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。**这些说明符放置在它们所修饰的类型之前。下面列出 C++ 程序中可用的存储类:

  • auto
  • register
  • static
  • extern
  • mutable
  • thread_local (C++11)

从 C++ 17 开始,auto 关键字不再是 C++ 存储类说明符,且 register 关键字被弃用。

auto 存储类

自 C++ 11 以来,auto 关键字用于两种情况:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。

C++98标准中auto关键字用于自动变量的声明,但由于使用极少且多余,在C++11中已删除这一用法。

根据初始化表达式自动推断被声明的变量的类型,如:

auto f=3.14;      //double
auto s("hello");  //const char*
auto z = new auto(9); // int*
auto x1 = 5, x2 = 5.0, x3='r';//错误,必须是初始化为同一类型

register 存储类

register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)。

{
   register int  miles;
}

寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义 ‘register’ 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。

static 存储类

static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。

static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。

extern 存储类

extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 ‘extern’ 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。

当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。

extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候

mutable 存储类

mutable 说明符仅适用于类的对象,允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改。

thread_local 存储类

使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。

thread_local 说明符可以与 static 或 extern 合并。

可以将 thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义。

以下演示了可以被声明为 thread_local 的变量:

thread_local int x;  // 命名空间下的全局变量
class X
{
    static thread_local std::string s; // 类的static成员变量
};
static thread_local std::string X::s;  // X::s 是需要定义的
 
void foo()
{
    thread_local std::vector<int> v;  // 本地变量
}

参考资料

C++ 修饰符类型
C/C++ 中 volatile 关键字详解
C++中restrict修饰符作用
C++ 类访问修饰符
C++ 存储类

<think>好的,我现在需要解释C++中的protected访问修饰符。首先,我得回顾一下C++的访问控制机制,主要有public、private和protected三种。用户可能已经知道public和private的区别,但不太清楚protected的作用,特别是在继承中的表现。 用户的问题是关于protected修饰符的,我需要先给出基本定义。protected成员在类外部不可访问,这一点和private类似,但和private不同的是,protected成员可以被派生类访问。这点很关键,尤其是在继承体系中,派生类需要访问基类的某些成员时,protected就派上用场了。 接下来,我需要比较protected与public和private的区别。public成员在任何地方都可以访问,private只有类自己和友元可以访问,而protected允许类自身、派生类以及友元访问。这里可能需要举例说明,比如基类中的protected成员在派生类中可以直接使用,而private成员不行。 然后,应该讨论继承中的protected。比如,当使用公有继承时,基类的public成员在派生类中变为public,protected成员保持protected,而private成员不可访问。如果是保护继承或私有继承,情况又会不同,但用户的问题可能更关注protected本身,所以可能需要简要提到不同继承方式的影响,但重点放在protected成员在派生类中的访问性。 还需要指出一些常见误区,比如派生类的对象不能直接访问基类的protected成员,只能通过派生类的成员函数访问。例如,如果有一个基类Base,里面有protected的变量x,派生类Derived的对象d不能直接d.x,但可以在Derived的成员函数中使用x。 另外,protected继承的概念可能需要解释,虽然用户的问题可能更多是关于成员的访问权限,但如果有必要,可以简要说明继承方式对访问权限的影响,比如使用protected继承时,基类的public和protected成员在派生类中都变成protected。 可能还需要提到友元类和友元函数可以访问protected成员,这和private的情况类似,但用户可能已经了解这一点,所以可以简要带过。 最后,总结protected的使用场景,比如当需要允许派生类访问基类的某些成员,同时又不向外部公开时,使用protected。这有助于实现封装和继承的结合,增强代码的可维护性。 在组织内容时,需要结构清晰,分点说明,可能使用示例代码来辅助解释,但根据用户提供的代码示例,可能更倾向于文字描述,但适当加入代码片段会更直观。同时需要确保用词准确,避免歧义,比如强调派生类中的成员函数可以访问基类的protected成员,而对象实例不行。 还需要检查是否有常见的错误或注意事项,比如在派生类中覆盖基类的protected虚函数时的可见性,或者设计上的考量,比如过度使用protected可能导致封装性降低等。这些可以作为进阶内容提及,但用户可能更关注基础概念,所以需要平衡深度和广度。 最后,确保回答符合用户之前的格式要求,使用中文,结构清晰,分步骤或分点说明,可能使用加粗或列表来突出重点,但避免使用markdown格式。同时注意之前的对话历史,用户提供的代码中存在构造函数参数传递错误,可能在解释protected时,可以结合类似的例子,说明protected成员在继承中的正确使用,避免类似的错误。 现在需要将以上思考整理成一个连贯的回答,分点解释protected的作用、与其它修饰符的区别、继承中的表现、使用注意事项以及示例说明,确保覆盖用户可能关心的各个方面,同时指出常见错误和最佳实践。</think>以下是关于C++中`protected`访问修饰符的详细说明: --- ### **一、基本概念** `protected`是C++中的**访问控制修饰符**,用于控制类成员的可见性。其核心规则如下: - **类内部**:可直接访问(与`public`行为一致) - **派生类内部**:可直接访问(与`private`的关键区别) - **类外部**(包括对象实例):**不可访问**(与`private`行为一致) --- ### **二、与`public`/`private`的对比** | 修饰符 | 类内部 | 派生类 | 类外部 | |------------|--------|--------|--------| | `public` | ✔️ | ✔️ | ✔️ | | `protected`| ✔️ | ✔️ | ❌ | | `private` | ✔️ | ❌ | ❌ | --- ### **三、继承中的行为** #### 1. **基类成员的可见性变化** 继承方式会影响基类成员在派生类中的访问级别: - **公有继承(`public`)**: ```cpp class Base { protected: int x; // 基类protected成员 }; class Derived : public Base { void func() { x = 10; // ✔️ 可直接访问基类的protected成员 } }; ``` - **保护继承(`protected`)**: ```cpp class Derived : protected Base { // Base的public成员在此变为protected }; ``` - **私有继承(`private`)**: ```cpp class Derived : private Base { // Base的public/protected成员在此变为private }; ``` #### 2. **对象实例的限制** 即使通过继承,**派生类的对象仍无法直接访问基类的`protected`成员**: ```cpp Derived d; d.x = 10; // ❌ 编译错误!必须通过派生类的成员函数访问 ``` --- ### **四、典型使用场景** 1. **实现继承** 允许派生类直接操作基类的内部状态,同时对外隐藏细节。 *示例:基类`Shape`中定义`protected`的坐标数据,派生类`Circle`可直接修改这些坐标。* 2. **模板方法模式** 基类声明`protected`虚函数,派生类实现具体逻辑: ```cpp class Animal { protected: virtual void makeSound() = 0; // 由派生类实现 public: void speak() { makeSound(); } // 统一调用接口 }; ``` --- ### **五、注意事项** 1. **谨慎设计** 过度使用`protected`会破坏封装性,建议优先通过公有方法提供访问控制。 2. **友元例外** 友元类/函数可以访问`protected`成员: ```cpp class FriendClass { friend void specialAccess(); // 友元函数可访问所有成员的protected/private }; ``` 3. **构造/析构函数** 派生类的构造函数**无法直接初始化基类的`private`成员**,但可通过基类构造函数间接初始化: ```cpp class Base { protected: int value; // 允许派生类构造函数初始化 }; class Derived : public Base { public: Derived(int v) : Base{v} {} // ✔️ 正确方式 }; ``` --- ### **六、常见错误示例** ```cpp class Base { protected: int secret; }; class Derived : public Base { public: void leakSecret(Base& other) { other.secret = 5; // ❌ 错误!不能通过其他对象访问protected成员 } }; ``` *错误原因*:派生类只能访问**自己继承的**`protected`成员,不能访问其他对象(包括基类对象)的`protected`成员。 --- 通过合理使用`protected`,可以在继承体系中实现**受控的代码复用**,平衡封装性与扩展性的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值