假設定義了一個類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;
}