由C++的const修饰引开来

假设定义了一个类HelloWorld

// helloworld.h
#ifndef HELLOWORLD_H
#define HELLOWORLD_H
class HelloWorld
{
public:
    HelloWorld();

    int age() const;
    void setAge(int age);

private:
    int m_age;
};
#endif // HELLOWORLD_H
// helloworld.cpp
#include "helloworld.h"

HelloWorld::HelloWorld()
{
}

int HelloWorld::age() const
{
    return m_age;
}

void HelloWorld::setAge(int age)
{
    m_age = age;
}

则以下代码将会报错 error: ‘this’ argument to member function ‘setAge’ has type ‘const HelloWorld’, but function is not marked const:

const HelloWorld hw;
hw.setAge(29);
hw.age();

因为hw是const对象,而setAge是非const函数,也就是说setAge有可能修改hw对象的成员数据,而const对象是不允许修改的。

假设类中定义了这样的成员数据:

private:
	static const int m_weight;

则需要使用

const int HelloWorld::m_weight = 123;

进行初始化。

如果是:

private:
	const int m_weight;

则需要在构造函数初始化列表初始化:

HelloWorld::HelloWorld() : m_weight(123)
{
}

但是在c++11中,不管是const还是static const,都可以直接在定义的时候初始化:

private:
	const int m_weight = 123;

另外,如果是在函数返回值前加const,则表示函数返回值是const,注意与函数后加const的区别,如下代码:

// helloworld.h
const int *getPtr();

// helloworld.cpp
const int *HelloWorld::getPtr()
{
    int *ptr = new int(123);
    return ptr;
}

则以下代码会报错 error: read-only variable is not assignable:

const int *ptr = hw.getPtr();// int const *ptr = hw.getPtr(); int const *和 const int *是一样的
*ptr = 28;

因为此处const表示ptr指向的值是常量,而这个值是不可以被修改的。但ptr本身作为指针是可以被修改的,如:

ptr = &age;

类似的,const修饰指针还有另一种表示,这种情况下指针所指向的值就是可以被修改的,如:

int * const const_ptr = new int(123);
*const_ptr = 28;

但const_ptr本身则不能被修改,即以下会报错:

const_ptr = &age;

在函数参数传递中,如果是值传递,则会发生对象拷贝,所以最好是用const &,即常引用代替。

C++中的内存分为:栈、堆和静态存储区。

栈就是在函数中定义的对象,就是栈对象,由系统帮我们销毁;

而堆就是用new申请的对象,内存的销毁由程序员管理,如果使用完没有销毁,则会发生内存

泄漏,如果销毁后,指针没有置为nullptr,则指针会变成野指针;

比较容易混淆的就是静态存储区,那么什么是静态存储区呢?

静态存储区包括了:静态对象和全局对象。

全局对象就是在类外声明定义的对象,比如:

static int s_int = 0;
const int g_int = 1;
int n_int = 2;

以上三个变量都是全局对象。

而局部静态对象,比如:

void HelloWorld::setAge(int age)
{
    static int s_infunc_int = age;
    m_age = age;
}

则s_infunc_int的生命期是在setAge函数第一次被调用时开始,知道程序结束。

静态对象还有一种类静态对象,不同的类实例会共用同一个静态对象,通过代码来理解,假设有如下基类Base和继承类Deverived:

// base.h
#ifndef BASE_H
#define BASE_H
class Base
{
public:
    Base();

    int age() const;
    void setAge(int age);

private:
    static int m_age;
};
#endif // BASE_H
// base.cpp
#include "base.h"

int Base::m_age = 28;

Base::Base()
{

}

int Base::age() const
{
    return m_age;
}

void Base::setAge(int age)
{
    m_age = age;
}

// deverived.h
#ifndef DEVERIVED_H
#define DEVERIVED_H

#include "base.h"

class Deverived : public Base
{
public:
    Deverived();
};

#endif // DEVERIVED_H
// deverived.cpp
#include "deverived.h"

Deverived::Deverived()
{

}

则以下三个对象共享同一个静态对象m_age:

Base b;
Deverived d;
Deverived d2;

即对它们调用age输出是一样的。

关于C++对象的内存分析,这篇Blog的分析可以看下C++对象内存分配问题

谈到const和static,不得不说extern,这三个修饰符感觉是三兄弟,联系十分紧密。

言归正传,第一个,extern可以用来进行链接指定,当它与"C"一起使用时,表示告诉编译器按照C语言的规则区翻译函数名,因为如果按照C++的规则区翻译的话,会根据函数名和参数类型生成一个新的名称,这是C++为了支持函数的重载,所以对于一些用C语言实现的库函数,必须使用extern "C"进行声明,如:

extern "C" void exCFunc();

另外一个需要注意的是,当链接一些C语言导出的库的时候,必须加上extern “C”{},否则会提示无法解析的库函数,比如链接ffmpeg库的时候,需要这样:

#ifdef __cplusplus
extern "C" {
#endif

#include <libavutil/avutil.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>

#ifdef __cplusplus
}
#endif

当初也是使用ffmpeg时候,查了很久才发现是这个原因。

extern的另一种作用就是只声明不定义,还是通过代码来理解:

// helloworld.cpp
extern int ex_variable = 12345;

或者

// helloworld.h
extern int ex_baby;

// helloworld.cpp
extern int ex_variable = 22223333;

然后在其他cpp中使用,如:

// main.cpp
extern int ex_variable;
int main()
{
	cout << "Hello World!\t" << ex_variable << endl;
}

注意⚠️,helloworld.cpp中的extern可加可不加,加了效果也是一样的,但helloworld.h和main.cpp中的extern是必须加的,否则会出现重复定义的编译错误。

extern和static不能同时修饰一个变量。

同时static有一个区别于extern的现象,看代码:

// helloworld.h
static char s_chArray[10];

// helloworld.cpp
void func1()
{
    s_chArray[0] = 'a';
    cout<<s_chArray<<endl;
}

// main.cpp
void func2()
{
    cout<<s_chArray<<endl;
}

输出:

func1	a23456
func2	123456

这个现象告诉我们,使用static最好在源文件中定义,而不是在头文件。

参考

volatile关键字,此关键字表示变量是易变的,告诉系统每次从内存中重新读取,而不是读取寄存器中的值,因为寄存器中的值可能是旧的,这种情况大多发生在多线程,并发环境下或者有中断程序。

Q:一个参数既可以是 const 还可以是 volatile 吗?为什么?
A:可以。一个例子是只读的状态寄存器。它是 volatile 因为它可能被意想不到地改变。它是 const 因为程序不应该试图去修改它。

Q:一个指针可以是 volatile 吗?为什么?
A:可以。尽管这并不常见。一个例子是当一个中断服务子程序修该一个指向一个buffer的指针时。

Q:下面的函数有什么错误?

int square(volatile int *ptr) 
{ 
	return *ptr * *ptr; 
} 

由于ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int *ptr) 
{ 
	int a,b; 
	a = *ptr; 
	b = *ptr; 
	return a * b; 
} 

由于 *ptr 的值可能被意想不到地改变,因此 a 和 b 可能是不同的。结果,这段代码可能返回的不是你所期望的平方值!正确的代码如下:

int square(volatile int *ptr) 
{ 
	int a=*ptr; 
	return a * a; 
} 
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值