由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; 
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章