天天看點

由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; 
} 
           

繼續閱讀