跳转至

cpp关键字

5、mutable

mutable的中文意思是“可变的,易变的”,跟constant(既C++中的const)是反义词。

在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中

我们知道,如果**类的成员函数不会改变对象的状态,那么这个成员函数一般会声明成const的。但是,有些时候,我们需要在const的函数里面修改一些跟类状态无关的数据成员,那么这个数据成员就应该被mutalbe来修饰**。

下面是一个小例子:

class ClxTest
{
 public:
  void Output() const;
};

void ClxTest::Output() const
{
 cout << "Output for test!" << endl;
}

void OutputTest(const ClxTest& lx)
{
 lx.Output();
}

类ClxTest的成员函数Output是用来输出的,不会修改类的状态,所以被声明为const的。

函数OutputTest也是用来输出的,里面调用了对象lx的Output输出方法,为了防止在函数中调用其他成员函数修改任何成员变量,所以参数也被const修饰。

如果现在,我们要增添一个功能:计算每个对象的输出次数。如果用来计数的变量是普通的变量的话,那么在const成员函数Output里面是不能修改该变量的值的;而该变量跟对象的状态无关,所以应该为了修改该变量而去掉Output的const属性。这个时候,就该我们的mutable出场了——只要用mutalbe来修饰这个变量,所有问题就迎刃而解了。

下面是修改过的代码:

class ClxTest
{
 public:
  ClxTest();
  ~ClxTest();

  void Output() const;
  int GetOutputTimes() const;

 private:
  mutable int m_iTimes;
};

ClxTest::ClxTest()
{
 m_iTimes = 0;
}

ClxTest::~ClxTest()
{}

void ClxTest::Output() const
{
 cout << "Output for test!" << endl;
 m_iTimes++;
}

int ClxTest::GetOutputTimes() const
{
 return m_iTimes;
}

void OutputTest(const ClxTest& lx)
{
 cout << lx.GetOutputTimes() << endl;
 lx.Output();
 cout << lx.GetOutputTimes() << endl;
}

计数器m_iTimes被mutable修饰,那么它就可以突破const的限制,在被const修饰的函数里面也能被修改

本文转自 https://blog.csdn.net/aaa123524457/article/details/80967330,如有侵权,请联系删除。

8、const

const取自constant的缩写,本意是不变的,不易改变的意思

一.修饰普通变量

const int a = 7;
int b = a; //正确
a = 8; // 错误,不能赋值

二.修饰指针

int b = 500;
const int* a = &b;
int const *a = &b;

这两种一样的,const修饰的是*a ,无法修改*a的值,*a指的其实是b的值
就是无法给*a赋值,*a=10会报错
但是能给b赋值,而且赋值后*a也会跟着变,但是不管如何只能读取*a,而不能给*a赋值

int* const a = &b;
const修饰的是a,既指针本身,就是a(地址)无法被修改,但是*a可以变,b也可以变

const int* const a = &b;
为指针本身和指向的内容均为常量

三.修饰类的成员函数时

为什么仅仅说是类中的成员函数?因为非成员函数不能用const修饰,在cpp文件下直接写一个函数带const会报错

在类中将成员函数修饰为const表明在该函数体内,不能修改对象的数据成员而且不能调用非const函数。为什么不能调用非const函数?因为非const函数可能修改数据成员,const成员函数是不能修改数据成员的,所以在const成员函数内只能调用const函数。

class A
{
private:
    int i;
public:
    void setnum(int n) //set函数需要设置i的值,所以不能声明为const
    {
        i = n;
    }

    int getnum() const //get函数返回i的值,不需要对i进行修改,则可以用const修饰。防止在函数体内对i进行修改。
    {
        //i = 30;  会报错,这句要删掉
        //Func();  这句也会报错,不能调非const的函数,“对象含有与成员 函数 "A::func" 不兼容的类型限定符
        return i;
    }

    int Func()
    {
        return 0;
    }
};

四.const修饰函数参数

1.防止传入的参数代表的内容在函数体内被改变,但仅对指针和引用有意义。

因为如果是按值传递,传给参数的仅仅是实参的副本,即使在函数体内改变了形参,实参也不会得到影响。如:
void fun(const int i)
{
i = 10;
}
在函数体内是不能改变 i 的值的,但是没有任何实际意义,因为按值传递实参的值本来就不会改变,加不加const没有区

2.const修饰的函数参数是指针时,代表在函数体内不能修改该指针所指的内容,起到保护作用

如下:在字符串复制的函数中保证不修改源字符串的情况下,实现字符串的复制
void fun(const char * src, char * des) //保护源字符串不被修改,若修改src则编译出错。
{
strcpy(des,src);
}
而且const指针可以接收非const和const指针,而非const指针只能接收非const指针

3.const修饰引用时

当传参的类型为自定义类型,比如A是自定义的类,如下:
void fun(A a)
{
}
那么此时,传递进来的参数a是实参对象的副本,
要调用构造函数来构造这个副本,而且函数结束后要调用析构函数来释放这个副本,在空间和时间上都造成了浪费
所以函数参数为类对象的情况,推荐用引用。
但是按引用传递,又可能会造成安全隐患,通过函数参数的引用可以修改实参的内部数据成员,所以用const来保护实参,如下:
void fun(A& a)
{
}

五.修饰函数返回值

也是用const来修饰返回的指针或引用,保护指针指向的内容或引用的内容不被修改,也常用于运算符重载。归根究底就是**使得函数调用表达式不能作为左值。**

class A 
{
private:
    int i;
public:
    A() 
    { 
        i = 0;
    }
    int & get() 
    {
        return i;
    }

    const int & get_i()
    {
        return i;
    }
};

void main()
{
    A a;
    cout << a.get() << endl; //数据成员值为0
    a.get() = 1; //尝试修改a对象的数据成员为1,而且是用函数调用表达式作为左值。
    cout << a.get() << endl; //数据成员真的被改为1了,返回指针的情况也可以修改成员i的值,所以为了安全起见最好在返回值加上const,使得函数调用表达式不能作为左值

    a.get_i() = 1;//这一句会报错,因为是const修饰,不能修改函数返回值
}

本文转自 https://www.cnblogs.com/kevinWu7/p/10163449.html,如有侵权,请联系删除。

10、explicit关键字

首先, C++中的explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是**表明该构造函数是显示的**,

explicit关键字的作用就是防止类构造函数的隐式自动转换.

而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式).

那么显示声明的构造函数和隐式声明的有什么区别呢? 我们来看下面的例子:

class CxString  // 没有使用explicit关键字的类声明, 即默认为隐式声明  
{  
public:  
    char *_pstr;  
    int _size;  
    CxString(int size)  
    {  
        _size = size;                // string的预设大小  
        _pstr = malloc(size + 1);    // 分配string的内存  
        memset(_pstr, 0, size + 1);  
    }  
    CxString(const char *p)  
    {  
        int size = strlen(p);  
        _pstr = malloc(size + 1);    // 分配string的内存  
        strcpy(_pstr, p);            // 复制字符串  
        _size = strlen(_pstr);  
    }  
    // 析构函数这里不讨论, 省略...  
};  

    // 下面是调用:  

    CxString string1(24);     // 这样是OK的, 为CxString预分配24字节的大小的内存  
    CxString string2 = 10;    // 这样是OK的, 为CxString预分配10字节的大小的内存  
    CxString string3;         // 这样是不行的, 因为没有默认构造函数, 错误为: “CxString”: 没有合适的默认构造函数可用  
    CxString string4("aaaa"); // 这样是OK的  
    CxString string5 = "bbb"; // 这样也是OK的, 调用的是CxString(const char *p)  
    CxString string6 = 'c';   // 这样也是OK的, 其实调用的是CxString(int size), 且size等于'c'的ascii码  
    string1 = 2;              // 这样也是OK的, 为CxString预分配2字节的大小的内存  
    string2 = 3;              // 这样也是OK的, 为CxString预分配3字节的大小的内存  
    string3 = string1;        // 这样也是OK的, 至少编译是没问题的, 但是如果析构函数里用free释放_pstr内存指针的时候可能会报错, 完整的代码必须重载运算符"=", 并在其中处理内存释放  

上面的代码中, "CxString string2 = 10;" 这句为什么是可以的呢? 在C++中, 如果的构造函数只有一个参数时, 那么在编译的时候就会有一个缺省的转换操作:将该构造函数对应数据类型的数据转换为该类对象. 也就是说 "CxString string2 = 10;" 这段代码, 编译器自动将整型转换为CxString类对象, 实际上等同于下面的操作:

CxString string2(10);  
  
CxString temp(10);  
CxString string2 = temp; 

但是, 上面的代码中的_size代表的是字符串内存分配的大小, 那么调用的第二句 "CxString string2 = 10;" 和第六句 "CxString string6 = 'c';" 就显得不伦不类, 而且容易让人疑惑. 有什么办法阻止这种用法呢? 答案就是使用explicit关键字. 我们把上面的代码修改一下, 如下:

class CxString  // 使用关键字explicit的类声明, 显示转换  
{  
public:  
    char *_pstr;  
    int _size;  
    explicit CxString(int size)  
    {  
        _size = size;  
        // 代码同上, 省略...  
    }  
    CxString(const char *p)  
    {  
        // 代码同上, 省略...  
    }  
};  

    // 下面是调用:  

    CxString string1(24);     // 这样是OK的  
    CxString string2 = 10;    // 这样是不行的, 因为explicit关键字取消了隐式转换  
    CxString string3;         // 这样是不行的, 因为没有默认构造函数  
    CxString string4("aaaa"); // 这样是OK的  
    CxString string5 = "bbb"; // 这样也是OK的, 调用的是CxString(const char *p)  
    CxString string6 = 'c';   // 这样是不行的, 其实调用的是CxString(int size), 且size等于'c'的ascii码, 但explicit关键字取消了隐式转换  
    string1 = 2;              // 这样也是不行的, 因为取消了隐式转换  
    string2 = 3;              // 这样也是不行的, 因为取消了隐式转换  
    string3 = string1;        // 这样也是不行的, 因为取消了隐式转换, 除非类实现操作符"="的重载  

explicit关键字的作用就是防止类构造函数的隐式自动转换.

上面也已经说过了, explicit关键字只对有一个参数的类构造函数有效, 如果类构造函数参数大于或等于两个时, 是不会产生隐式转换的, 所以explicit关键字也就无效了.

例如:

class CxString  // explicit关键字在类构造函数参数大于或等于两个时无效  
{  
public:  
    char *_pstr;  
    int _age;  
    int _size;  
    explicit CxString(int age, int size)  
    {  
        _age = age;  
        _size = size;  
        // 代码同上, 省略...  
    }  
    CxString(const char *p)  
    {  
        // 代码同上, 省略...  
    }  
};  

    // 这个时候有没有explicit关键字都是一样的  

但是, 也有一个例外, 就是当除了第一个参数以外的其他参数都有默认值的时候, explicit关键字依然有效, 此时, 当调用构造函数时只传入一个参数, 等效于只有一个参数的类构造函数, 例子如下:

class CxString  // 使用关键字explicit声明  
{  
public:  
    int _age;  
    int _size;  
    explicit CxString(int age, int size = 0)  
    {  
        _age = age;  
        _size = size;  
        // 代码同上, 省略...  
    }  
    CxString(const char *p)  
    {  
        // 代码同上, 省略...  
    }  
};  

    // 下面是调用:  

    CxString string1(24);     // 这样是OK的  
    CxString string2 = 10;    // 这样是不行的, 因为explicit关键字取消了隐式转换  
    CxString string3;         // 这样是不行的, 因为没有默认构造函数  
    string1 = 2;              // 这样也是不行的, 因为取消了隐式转换  
    string2 = 3;              // 这样也是不行的, 因为取消了隐式转换  
    string3 = string1;        // 这样也是不行的, 因为取消了隐式转换, 除非类实现操作符"="的重载  

13、volatile

在 C++ 中,volatile 是一个关键字,用于**告诉编译器该变量的值可能会在程序的其他部分被修改,因此编译器不应该对该变量进行优化,例如缓存该变量的值,而应该每次都从内存中读取该变量的值**。

volatile 通常**用于多线程编程或者与硬件交互的程序中**,因为在这些情况下,变量的值可能会在程序的其他部分被修改,而编译器可能会对变量进行优化,导致程序出现错误。使用 volatile 可以避免这种情况发生。

需要注意的是,volatile 并不是线程安全的解决方案,它只是**告诉编译器该变量可能会被修改,编译器会相应地生成代码**,但是如果多个线程同时修改该变量,仍然需要使用其他的线程安全机制来保证程序的正确性。