假设定义了一个类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
#include
#include
#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<
输出:
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;
}