在C/C++中,變數、函數和後面要學到的類都是大量存在的,這些變數、函數和類的名稱將都存在於全域性作用域中,可能會導致很多衝突。使用名稱空間的目的是對識別符號的名稱進行在地化,以避免命名衝突或名字汙染,namespace關鍵字的出現就是針對這種問題的。
舉個例子:
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
int main()
{
printf("%d", rand);
return 0;
}
程式編譯的結果顯示rand重定義了,為什麼會這樣呢?因為在stdlib.h這個標頭檔案中已經定義了rand這樣一個函數,這樣就導致了編譯器不知道這是一個函數還是一個變數,C語言中無法應對這種衝突,只能通過改名字來避免。
而C++為了解決這個問題,引入了名稱空間的概念。
定義名稱空間,需要使用到namespace關鍵字,後面跟名稱空間的名字,然後接一對{}即可,{}中即為名稱空間的成員。
//名稱空間
//A就是名稱空間的名字
namespace A{
int a;
void func()
{}
}
注意事項:
接下來給一個完整的程式碼塊來展示名稱空間的注意事項和使用:
#include<iostream> //引入標頭檔案
#include<string>//C++中的字串
using namespace std; //標準名稱空間
//A就是名稱空間的名字
namespace A
{
//名稱空間內既可以定義變數也可以定義函數
int rand = 10;
int Sub(int x, int y)
{
return x + y;
}
struct ListNode
{
int data;
struct ListNode* next;
};
}
//名稱空間的巢狀定義
namespace B
{
int rand;
namespace C
{
int rand;
}
}
//名稱空間是開放的,隨時可以加入新成員,但是新成員只能在加入後使用
namespace B
{
int c;//此時c會合併到名稱空間B中,實際上就是個合併的過程
}
//匿名名稱空間
namespace
{
int d = 5;//名稱空間沒有名字,就類似於static int d = 50,是個靜態全域性變數,別的檔案無法使用
}
int main()
{
//名稱空間的使用
//1.::作用域限定符
//存取A空間的Sub函數
cout << A::Sub(10, 20) << endl;
//2.存取巢狀空間
//存取B空間的C空間的rand變數
B::C::rand = 5;
cout << B::C::rand << endl;
system("pause");
return EXIT_SUCCESS;
}
引入using關鍵字之後,名稱空間的使用又變得不一樣
namespace A
{
int a = 10;
}
void test01()
{
//using宣告的意思就是讓名稱空間中某個識別符號可以直接使用
using A::a;
cout<<a<<endl;
}
注意:
1.使用using宣告,就不會每次寫A::a了,直接用a就可以
2.using A::a宣告的意思就是把變數a又在test函數中定義了一次,此時如果在test內部再定義一個int a;就會出錯
namespace A
{
int a = 10;
}
using namespace A;
void test01()
{
cout<<a<<endl;
}
使用using關鍵字修飾namespace整個名稱空間,實際上就是脫去了這個名稱空間的外衣,就等價於你定義了一個int a在全域性
思考一個問題:下面程式碼有錯嗎?
在test01函數體內又定義了一個int a,會報錯麼?如果不報錯,那麼輸出的是全域性的 a = 10 還是區域性的a = 20?
namespace A
{
int a = 10;
}
using namespace A;
void test01()
{
int a = 20;
cout<<a<<endl;
}
答案是不會報錯,輸出的是區域性的20,因為名稱空間A內部的變數a在使用using關鍵字後相當於在全域性定義了一個int a ;而在函數體內定義一個區域性的 int a;兩個變數的作用域不同,是可以定義同名變數的,輸出的是區域性變數的值,小夥伴的要注意區分~
#include <iostream>
using namespace std;// 將std標準名稱空間進行展開
int main()
{
cout << "hello world" << endl;
// std::cout << "hello world" << endl; 也可以這樣寫就不展開std標準名稱空間
return 0;
}
使用C++輸入輸出更方便,不需增加資料格式控制,比如:整形–%d,字元–%c
int main()
{
int a = 0;
double b = 0.0;
cin >> a;
cin >> b;
cout << "a = " << a << " b = " << b << endl;
system("pause");
return EXIT_SUCCESS;
}
執行結果如下:
概念:
預設引數是宣告或定義函數時為函數的引數指定一個預設值。在呼叫該函數時,如果沒有指定實參則採用該預設值,否則使用指定的實參。
void PrintNum(int n = 0)
{
cout << n << endl;
}
int main()
{
PrintNum();// 無引數時,使用引數的預設值
PrintNum(10);// 有引數時,使用指定的實參
system("pause");
return EXIT_SUCCESS;
}
引數都要一個預設值,給定的實參依次從左向右給形參賦值
注意:我們在呼叫函數時,只能預設最右邊的若干個引數,形如:Fun(4, , 6);
這種呼叫是錯誤的呼叫方法。
void Func(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
int main()
{
// 實參從左向右一次給形參賦值
Func();
Func(1);
Func(1, 2);
Func(1, 2, 3);
system("pause");
return EXIT_SUCCESS;
}
執行結果如下:
只有部分形參給定了預設值,半預設引數必須從右往左依次來給出,不能間隔著給。
值得注意的是,預設引數只能為最右邊的若干個。
形如:void Fun(int a=10, int b, int c = 30) { }
這樣的語句是錯誤的用法。
形如:Fun(1, ,3)
這種呼叫也是錯誤的。
void Func(int a, int b = 10, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
int main()
{
Func(1);
Func(1, 2);
Func(1, 2, 3);
system("pause");
return EXIT_SUCCESS;
}
執行結果如下:
注意:
const修飾符的作用
規則
分類
常變數: const 型別說明符 變數名
常參照: const 型別說明符 &參照名
常物件: 類名 const 物件名
常成員函數: 類名::fun(形參) const
常陣列: 型別說明符 const 陣列名[大小]
常指標: const 型別說明符* 指標名 ,型別說明符* const 指標名
在C語言中const修改全域性變數是儲存在全域性區(即靜態儲存區),修飾區域性變數時儲存在棧區
//const修飾的常數
const int a = 10;//全域性const常數,放在常數區,受到常數區的保護
void test01()
{
//直接修改失敗
a = 100;
//間接修改失敗
int *p = &a;
*p = 100;
}
//區域性conts修飾常數
void test02()
{
conts int b = 10;//資料放在棧區,是個偽常數
//直接修改失敗
b = 100;
//間接修改成功
int *p = &b;
*p = 100;
}
總結:
在C++中編譯器會自動優化,會將常數的數值直接替換(類似於宏定義),這導致了const區域性變數與真實值產生了不一致。(常數摺疊現象),而C語言會先去記憶體中尋找,然後替換
舉個例子:
const int aa = 10;//沒有記憶體
void test01()
{
cout << aa << endl;//在編譯階段,編譯器會自動優化,將aa直接替換成常數10
const int bb = 20;//棧區
int *p = (int *)&bb;
*p = 200;
cout << bb << endl;//輸出的還是20,還是那句話,在編譯階段程式碼中的bb就已經全部被替換成20,此時其實輸出的是這樣的cout << 20 << endl;但是變數bb此時的值已經被改變了,變成了200,但是由於編譯器優化,造成了常數摺疊現象
}
總結:
例子一:用變數給const修飾的區域性變數賦值
void test03()
{
int a =10;
const int b = a;
int *p = (int *)&b;
*p = 100;
cout << b << endl;//輸出100
}
例子二:利用關鍵字阻止優化
void test04()
{
const volatile int a = 7;
int *p = (int *)(&a);
*p = 8;
cout << "a=" << a << endl;//輸出8
cout << "*p=" << *p;
system("pause");
return 0;
}
例子三:自定義資料型別不能優化
struct Maker
{
Maker()
{
a = 100;
}
int a;
};
void test05()
{
const Maker ma;
cout << ma.a <<endl;
Maker *p = (Maker*)&ma;
p->a = 200;//可以修改ma中的值
cout << ma.a << endl;
}
涉及到兩個很重要的概念,頂層const和底層const
從 const 指標開始說起。const int* pInt;
和 int *const pInt = &someInt;
,前者是 *pInt
不能改變,而後者是 pInt
不能改變。因此指標本身是不是常數和指標所指向的物件是不是常數就是兩個互相獨立的問題。用頂層表示指標本身是個常數,底層表示指標所指向的物件是個常數。
更一般的,頂層 const 可以表示任意的物件是常數,這一點對任何資料型別都適用;底層 const 則與指標和參照等複合型別有關,比較特殊的是,指標型別既可以是頂層 const 也可以是底層 const 或者二者兼備。
int a = 1;
int b = 2;
const int* p1 = &a;//指標常數(頂層const)
int* const p2 = &a;//常數指標(底層const)
1.指標常數(指標不可改,指標指向的物件可改)
int a = 10;
int b = 5;
int * const p1 = &a;
p1 = &b; //指標不可改,不合法
*p1 = b; //指標指向的物件可改,合法
2.常數指標(指標可改,指標指向的物件不可改)
int a = 10;
int b = 5;
const int* p2 = &a;
p2 = &b; //指標可改, 合法
*p2 = b; //不合法
拷貝與頂層和底層 const
int i = 0;
int *const p1 = &i; // 不能改變 p1 的值,這是一個頂層
const int ci = 42; // 不能改變 ci 的值,這是一個頂層
const int *p2 = &ci; // 允許改變 p2 的值,這是一個底層
const int *const p3 = p2; // 靠右的 const 是頂層 const,靠左的是底層 const
const int &r = ci; // 所有的參照本身都是頂層 const,因為參照一旦初始化就不能再改為其他物件的參照,這裡用於宣告參照的 const 都是底層 const
常參照所參照的物件不能更新,使用方法為:const 型別說明符 &參照名
非const參照只能繫結非const物件,const參照可以繫結任意物件,並且都當做常物件
常參照經常用作形參,防止函數內物件被意外修改。對於在函數中不會修改其值的引數,最好都宣告為常參照。複製建構函式的引數一般均為常參照
class Example{
public:
Example(int x, int y):a(x),b(y){}
Example(const Example &e):a(e.a),b(e.b){} //複製建構函式
void print();
void print() const;
private:
const int a,b;
static const int c = 10;
};
void Example::print() {cout<<"print():"<<a<<ends<<b<<endl;}
void Example::print() const {cout<<"print() const:"<<a<<ends<<b<<endl;}
const修飾引數是為了防止函數體內可能會修改引數原始物件。因此,有三種情況可討論:
void Fun(const A *in); //修飾指標型傳入引數
void Fun(const A &in); //修飾參照型傳入引數
void func (const int& n)
{
n = 10; // 編譯錯誤
}
const修飾函數返回值的含義和用const修飾普通變數以及指標的含義基本相同。這樣可以防止外部對 object 的內部成員進行修改。
const int* func() // 返回的指標所指向的內容不能修改
{
// return p;
}
由於C++會保護const物件不被更新,為了防止類的物件出現意外更新,禁止const物件呼叫類的非常成員函數。因此,常成員函數為常物件的唯一對外介面。
常成員函數的宣告方式:型別說明符 函數名(參數列) const
class A
{
public:
//返回值的型別是int &型別
int& getValue() const
{
// a = 10; // 錯誤
return a;
}
private:
int a; // 非const成員變數
};
注意事項:
類的資料成員不能在任何函數中被賦值或修改,但必須在建構函式中使用初始化列表的方式賦初值
,因為const修飾的物件必須初始化。
舉個例子,剛才的類如果a, b為常資料成員,則應該改寫為如下形式:
class Example{
public:
Example(int x, int y):a(x),b(y){} //初始化列表方式賦初值
void print();
void print() const;
private:
const int a,b;
};
void Example::print() {cout<<"print():"<<a<<ends<<b<<endl;}
void Example::print() const {cout<<"print() const:"<<a<<ends<<b<<endl;}
如果為靜態常資料成員,由於不屬於具體物件,所以不能在建構函式裡賦值,仍然應該在類外賦值。特別地,如果靜態常數為整數或列舉型別,C++允許在類內定義時指定常數值。
比如以下兩種方式均合法:
class Example{
public:
Example(int x, int y):a(x),b(y){}
void print();
void print() const;
private:
const int a,b;
static const int c = 10; //靜態常數
};
class Example{
public:
Example(int x, int y):a(x),b(y){}
void print();
void print() const;
private:
const int a,b;
static const int c; //靜態常數
};
const int Example::c = 10;
用const修飾的類物件,該物件內的任何成員變數都不能被修改。
因此不能呼叫該物件的任何非const成員函數,因為對非const成員函數的呼叫會有修改成員變數的企圖。
class A
{
public:
void funcA() {}
void funcB() const {}
};
int main
{
const A a;
a.funcB(); // 可以
a.funcA(); // 錯誤
const A* b = new A();
b->funcB(); // 可以
b->funcA(); // 錯誤
}
(1) 編譯器處理方式不同
define宏是在預處理階段展開。
const常數是編譯執行階段使用。
(2) 型別和安全檢查不同
define宏沒有型別,不做任何型別檢查,僅僅是展開。
const常數有具體的型別,在編譯階段會執行型別檢查。
(3) 儲存方式不同
define宏僅僅是展開,有多少地方使用,就展開多少次,不會分配記憶體。
const常數會在記憶體中分配(可以是堆中也可以是棧中)。
(4)作用範圍不同
const有作用域,而define不重視作用域,預設定義處到檔案結束,如果定義在指定作用域下有效的常數,那麼define不能用。
(5)const 可以節省空間,避免不必要的記憶體分配。 例如:
#define PI 3.14159 //常數宏
const doulbe Pi=3.14159; //此時並未將Pi放入ROM中 ......
double i=Pi; //此時為Pi分配記憶體,以後不再分配!
double I=PI; //編譯期間進行宏替換,分配記憶體
double j=Pi; //沒有記憶體分配
double J=PI; //再進行宏替換,又一次分配記憶體!
const定義常數從組合的角度來看,只是給出了對應的記憶體地址,而不是像#define一樣給出的是立即數,所以,const定義的常數在程式執行過程中只有一份拷貝,而 #define定義的常數在記憶體中有若干個拷貝。
static
1、static區域性變數 將一個變數宣告為函數的區域性變數,那麼這個區域性變數在函數執行完成之後不會被釋放,而是繼續保留在記憶體中
2、static 全域性變數 表示一個變數在當前檔案的全域性內可存取
3、static 函數 表示一個函數只能在當前檔案中被存取
4、static 類成員變數 表示這個成員為全類所共有
5、static 類成員函數 表示這個函數為全類所共有,而且只能存取靜態成員變數
static關鍵字的作用
(1)函數體內static變數的作用範圍為該函數體,該變數的記憶體只被分配一次,因此其值在下次呼叫時仍維持上次的值
(2)在模組內的static全域性變數和函數可以被模組內的函數存取,但不能被模組外其它函數存取
(3)在類中的static成員變數屬於整個類所擁有,對類的所有物件只有一份拷貝
(4)在類中的static成員函數屬於整個類所擁有,這個函數不接收this指標,因而只能存取類的static成員變數
const關鍵字的作用
(1)阻止一個變數被改變
(2)宣告常數指標和指標常數
(3)const修飾形參,表明它是一個輸入引數,在函數內部不能改變其值
(4)對於類的成員函數,若指定其為const型別,則表明其是一個常函數,不能修改類的成員變數
(5)對於類的成員函數,有時候必須指定其返回值為const型別,以使得其返回值不為」左值」
參照不是新定義一個變數,而是給已存在變數取了一個別名,編譯器不會為參照變數開闢記憶體空間,它和它參照的變數共用同一塊記憶體空間。也就好比我們給同學取了一個外號一樣。
用法: 型別&參照變數名(物件名)= 參照實體;
int main()
{
int a = 10;
int& ra = a;// ra是a的參照
cout << a << endl;
cout << ra << endl;
system("pause");
return EXIT_SUCESS;
}
Type& ref = val;
注意事項:
int a = 10;
int b = 20;
//int& ra;//參照必須初始化
int& ra = a;
int& rra = a;
//int& ra = b;//參照一旦初始化,不能改變它的指向
//int& raaa = NULL;//NULL本身就是不合法的,不能繫結不合法的空間
int arr[] = {1, 2, 3, 4, 5};
//三種方式
//1.定義陣列型別
typedef int(MY_ARR)[5];
MY_ARR &arref = arr;//建立參照,int &b = a
//2.直接定義參照
int(&arref2)[5] = arr;
//3.建立參照陣列型別
typedef int(&MY_ARR3)[5];
MY_ARR3 arref3 = arr;
//1.作為函數的引數
void func(int &a, int &b)
{
cout << a+b << endl;
}
//2.參照作為函數的返回值
int& func2()
{
int b = 10;//不能返回區域性變數的參照
int &p = b;
return p;//錯誤的
}
int& func3()
{
static int b = 10;
return b;
}
void test01()
{
int& q = func2();
cout << q << endl;
q = 100;
cout << q << endl;
func2() = 200;
cout << q << endl;
cout << func2() << endl;
//--------------上面的程式碼都是錯誤的,這裡解釋一下
//int& q = func2();實際上就是int&q = p,但是fun2函數執行完了之後,區域性變數全部被銷燬了,所以int&q = 就指向了一個非法的區域,但是編譯器沒有檢測出來,具體原因是什麼不清楚
func3() = 100;
cout << func3() << endl;
}
參照做返回值時,一般分兩種情況:返回的物件未歸還系統和返回的物件歸還系統。如果返回物件不歸還系統,我們可以參照返回,否則就需要傳值返回
想要返回區域性變數:1.把區域性變數寫在堆區 2.把區域性變數寫在靜態區
注意:
//普通參照
int a = 10;
int &ref = a;
ref = 20;
//int &ref2 = 10不能給字面量取別名,因為10這個字面量在記憶體中沒有空間,儲存在暫存器中
//常參照
const int &ref3 = 10;//可以給const修飾的參照賦予字面量
//編譯器會把上面的程式碼變為:int tmp = 10;const int &ref3 = tmp;
以inline修飾的函數叫做行內函式,編譯時C++編譯器會在呼叫行內函式的地方展開,沒有函數壓棧的開銷,行內函式提升程式執行的效率。
inline int Add(int x, int y)
{
return x + y;
}
int main()
{
int z = Add(1, 2);
return 0;
}
如果加了inline關鍵字,編譯器在編譯期間會用函數體替換函數的呼叫(類似於宏定義,在編譯階段就替換函數呼叫,將函數呼叫直接展開,減少了呼叫的時間,但是空間消耗巨大)。
下面是內斂函數的幾個特性:
1.inline是一種以空間換時間的做法,省去呼叫函數額開銷。所以程式碼很長或者有迴圈/遞迴的函數不適宜使用作為行內函式。
2.inline對於編譯器而言只是一個建議,編譯器會自動優化,如果定義為inline的函數體內有迴圈/遞迴等等,編譯器優化時會忽略掉內聯(是否稱為行內函式由編譯器決定)。
3.inline不建議宣告和定義分離,分離會導致連結錯誤。因為inline被展開,就沒有函數地址了,連結就會找不到。
例子:一個相同的函數,一個加了inline 關鍵字,一個沒加,加上一個函數要執行10條指令,文兩個函數分別呼叫1000次要執行多少條指令?
普通函數:1000+10(一次呼叫1次指令,加起來就是1000條,每次呼叫都是call函數,函數不展開就是10條)
行內函式:1000*10條指令(展開就是每次呼叫都是10條指令)
所以說,行內函式展開,會讓程式變大,所以程式碼很短的函數可以考慮有內聯,長函數和遞迴函數不適合用內聯。
C++11中,標準委員會賦予了auto全新的含義即:auto不再是一個儲存型別指示符,而是作為一個新的型別指示符來指示編譯器,auto宣告的變數必須由編譯器在編譯時期推導而得。
int a = 10;
auto b = a;// 自動推導b的型別為int
auto c = 'c';// 自動推導型別為char
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
//auto d;必須初始化
有一下幾種用法:
int a = 10;
// auto和auto*無區別
auto pa1 = &a;
auto* pa2 = &a;
auto& ra = a;// ==> int& ra = a;
cout << typeid(a).name() << endl;
cout << typeid(pa1).name() << endl;
cout << typeid(pa2).name() << endl;
cout << typeid(ra).name() << endl;
執行結果如下:
auto a = 3, b = 4;
auto c = 3.4, d = 5.5;
auto i =0, *p = &i;//正確,i是整型,p是整型指標
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
cout << typeid(i).name() << endl;
cout << typeid(p).name() << endl;
auto不能推導的兩個常見