C++對C的加強
1. C++命名空間基本常識
所謂namespace煮纵,是指標識符的各種可見范圍买决。C++標準庫中的所有標識符都被定義于一個名為std的namespace中擂红。
- 文件<iostream> 和 <iostream.h>格式不一樣,前者沒有后綴州既,實際上氮凝,在你的編譯器include文件夾中可以看到羔巢,二者是兩個文件,打開文件就會發(fā)現(xiàn)罩阵,里面的代碼是不一樣的竿秆。后綴為.h的頭文件C++標準已經(jīng)明確提出不支持了, 早些的實現(xiàn)將標準庫功能定義在全局空間里稿壁,聲明在帶.h后綴的頭文件里幽钢,C++標準為了和c區(qū)別開,也為了正確使用命名空間傅是,規(guī)定頭文件不使用帶后綴.h搅吁。因此:
- 1.1 當使用<iostream.h>時,相當于在c中調用庫函數(shù)落午,使用的是全局命名空間,也就是早期的c++實現(xiàn)肚豺;
- 1.2 當使用<iostream>時溃斋,該頭文件沒有定義全局命名空間,必須使用namespace std; 這樣債能正確使用cout吸申;
- 由于namespace的概念梗劫,使用C++標準庫的任何標識符時,可以有以下三種選擇:
- 2.1 直接指定標識符截碴。例如std::ostream 而不是ostream梳侨。完整語句如下:
std::cout << std:hex << 3.4 << std::endl;
- 2.2 使用關鍵字。using std::cout; using std::endl; using std::cin; 以上程序可以寫成
using std::cout;
using std::endl;
using std::cin;
cout << std::hex << 3.4 << endl;
- 2.3 最方便就是using namespace std; 例如:using namespace std; 這樣命名空間std內定義的所有標志符都有效(曝光)日丹。就好像他們被聲明為全局變量一樣走哺。那么以上語句可以如下寫:
using namespace std;
cout << hex << 3.4 <<endl;
因為標準庫非常龐大,所以程序員在選擇的類的名稱或者函數(shù)名時哲虾,就有可能和標準庫的某個名字相同丙躏。所以為了避免這種情況所造成的名字沖突择示,就把標準庫中的一切都放在名字空間std中。
但是晒旅,這樣又會帶來一個新問題:無數(shù)原有的C++代碼都依賴于使用了多年的偽標準庫中的功能栅盲,他們都是在全局空間下的。所以就有了<iostream.h> 和 <iostream>等等這樣的文件废恋,一個是為了兼容以前的C++代碼谈秫,一個是為了支持新的標準。 命名空間std封裝的是標準程序庫的名稱鱼鼓,標準程序庫為了和以前的頭文件區(qū)別拟烫,一般不加".h"。
總之蚓哩,標準C++引入關鍵字namespace(命名空間/名字空間/名稱空間/名域)构灸,可以更好地控制標識符的作用域。
既然提高的命名空間這個詞岸梨,不妨把C語言的命名空間和C++的命名空間兩者做一對比喜颁,就會更容易接受C++新標準這么指定使用規(guī)則的緣由:
C里面的命名空間:
- 在C語言中只有一個全局作用域;
- C語言中所有的全局標識符共享同一個作用域曹阔;
- 標識符之間可能發(fā)生沖突半开。
C++提出了命名空間的概念:
- 命名空間將全局作用域分成不同的部分;
- 不同命名空間的標識符可以同名而不會發(fā)生沖突赃份;
- 命名空間可以相互嵌套寂拆;
- 全局作用域也叫默認命名空間。
案例代碼:
#include "iostream"
// 文件iostream中沒有引入標準的std抓韩,所以需要我們程序員手工寫
using namespace std;
namespace namespaceA
{
int a = 10;
}
namespace namespaceB
{
int a = 20;
// namespace 也支持嵌套
namespace namespaceC
{
struct Teacher
{
char name[32];
int age;
} ;
}
}
int main()
{
cout << "namespace test" << endl;
/* using namespace namespaceA; */
cout << namespaceA::a << endl;
cout << namespaceB::a << endl;
using namespaceB::namespaceC::Teacher;
Teacher t1;
t1.age = 23;
}
2.“實用性”加強
C語言的變量都必須在作用于的開始位置定義纠永,但是C++更強調語言“實用性”,所有的變量都可以在需要的時候定義谒拴。
3. register關鍵字增強
在C語言中尝江,
- 1)register關鍵字請求“編譯器”將局部變量存儲于寄存器中;
- 2)C語言中無法獲得register變量的地址;
在C++語言中, 1)C++編譯器有自己的優(yōu)化方式英上,即使程序員不使用register也可能做優(yōu)化, 2)C++中可以獲得register變量的地址炭序。 - PS: 所謂的代碼優(yōu)化,就是如果變量出現(xiàn)在循環(huán)中被重復的使用苍日,那么編譯器在處理代碼時就會把變量的存放在寄存器中惭聂。
早期的計算機比較慢,編譯器不會對代碼進行優(yōu)化相恃,所以使用register來做一個人為地補充辜纲。
C++編譯器除了有自己的優(yōu)化代碼的方式,在發(fā)現(xiàn)程序中需要取register變量的地址的時候,register對變量的聲明變得無效侨歉。
#include "iostream"
using namespace std;
int main()
{
int nRet = 0;
register int a = 100;
printf("&a = %d\n", &a);
// 上面的語句在C語言中無法編譯通過屋摇,但是C++可以變異成功
return nRet;
}
4. 變量檢測增強
C語言里面的一個“灰色地帶”:重復定義多個同名的全局變量是合法的;
C++中編譯器不允許定義多個同名的全局變量幽邓,會直接拒絕這種二義性的做法炮温。
int g_a;
int g_a = 100;
void main()
{
printf("hello world...\n");
}
5.struct類型增強
C語言不認為預先定義的struct是一種數(shù)據(jù)類型,所以我使用typedef來自己定義一個數(shù)據(jù)類型牵舵。
C++中就把struct關鍵字定義的struct直接增強為一種數(shù)據(jù)類型柒啤;
另外還加強的一點是,struct和class一樣畸颅,可以對自己的元素設定訪問權限:public, protected, private担巩。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Teacher
{
char name[64];
int age;
};
struct Student
{
char name[64];
int id;
};
// advTeacher展示了增強的struct和class的相似性,區(qū)別以后再表
struct advTecher
{
public:
char name[64];
int id;
protected:
int class;
private:
char age;
};
int main()
{
Teacher t1; //編譯器通過
Student s1; //編譯器報錯
struct Teacher s1; //這樣編譯器才通過
}
6. 三目運算符增強
在C語言中没炒,運算符的結果本質上就是一個數(shù)值涛癌,它不是變量,運算符表達式的結果直接保存在寄存器中送火。如果程序員試圖把它當做左值來賦值操作拳话,編譯器會報錯碍现;
C++中運算符的結果本質上被增強成為一個變量抗斤,它保存在內存中瞎疼,是可以當做左值被修改數(shù)值的传轰。
那么這是如何從C語言增強的?
其實就是C++的設計者們改變了運算符的返回對象措译,把C語言中只是返回一個數(shù)值改變?yōu)槁鸷疲祷匾粋€變量的地址揩页,代碼如下所示:
int a = 10, b = 20;
(a < b ? a : b)
// 改為
(a < b ? &a : &b)
這樣一來猖败,盡管運算符返回的仍然是一個數(shù)值速缆,但是這個數(shù)值是一個內存地址,我們想把它當做左值來進行賦值操作恩闻,只需要在地址的前面加上*取地址符即可艺糜。
*(a < b ? &a : &b) = 50;
這句話在C語言里面也是可以通過編譯并且能夠成功執(zhí)行的。C++的增強實質就是C++編譯器幫助程序員們完成了這個操作判呕。
而當運算符是右值的時候,就保持C語言中的處理方式送滞。
7. 對關鍵字const加強
為了盡可能搞清楚這個話題侠草,我把之前的C語言筆記直接搬回來:
辨析一下const修飾的位置:
const int a;
int const a;
上面這兩句話的作用是相同的。
const int *a;
int const *a;
這兩句話句話也是同樣的作用:指針指向地址的內容不能更改犁嗅,指針指向的地址可以更改边涕。
int *const a;
const修飾的是a:a指向的地址不可以更改,但是地址里面的內容可以更改。
總結了以下規(guī)律:
- 只看const后面的內容,const 后面是個a功蜓,a本身是一個指針變量园爷,所以就是指針的值不能改了;
- const后面是*a 或int *a,就是說*a不能改,即a指向的內存的內容不能更改式撼。
int main()
{
const int a; // 整型變量a的值不可修改
int const b; // 同上
const char *c; // 這樣的代碼按照“從右往左”的順序開始理解即可避免概念混淆
char *const d; // const char *c = const (char *c): char *c 指向的是一個存儲char類型的值的內存童社,
// 再用const修飾這個內存地址,意思就是:char *c 指向的內存地址不可以被更改著隆,
// 但是指針c可以指向其他的內存地址扰楼,指向了新的內存地址后,新的內存的內容也就不能更改美浦。
// 同理弦赖,char *const d = char (*const d): *d是一個指針變量(類型不明,指向不明浦辨,
// 但是已經(jīng)為d分配了內存空間)蹬竖。那么就是d的指向不能改變,d只能一直指向一個固定
// 的內存空間流酬,再用char 來修飾說明币厕,這個d指向的內存空間存儲char 類型的值,
// 這個內存空間存儲的值當然可以修改的
const char *const e; // 同理康吵,const char *const e = {const [char *(const e)]}:
// e的值不變劈榨,e是一個指針,指針e指向一個char類型變量晦嵌,
// 指針e指向的char變量是不能更改的同辣。
}
下面直接說這個話題的結論:
const關鍵字在C語言編譯器器中并不能真正起到鎖定變量值的作用,因為我們只要使用一個變量同類型的指針變量p惭载,用p即可直接修改變量的值旱函;*
但是在C++編譯器中真正做到了鎖定變量的值,同樣的代碼描滔,使用p來修改變量值棒妨,然后打印結果,發(fā)現(xiàn)變量的值不變含长。*
void main()
{
const int a = 10;
int *p = NULL;
p = (int *)&a;
*p = 9999;
printf("a: %d \n", a);
// C語言編譯器處理后運行結果:a: 9999
// C++編譯器處理后運行結果:a: 10
}
下面我們來把C++編譯器的工作解釋一下:
在使用了const關鍵字修飾變量后券腔,C++編譯器會把變量存放在“符號表”中,符號表里面存儲的都是鍵值對:key-value拘泞。
注意:這里的“符號表”是和之前的“內存四區(qū)”是兩個概念纷纫,不要拿來試圖相互說明。
但是在代碼
p = (int *)&a;
中執(zhí)行的是什么操作呢陪腌?編譯器會再分配一個新的空間來存放當前a的值辱魁,所以p獲取的內存地址和a并沒有關系烟瞧,所以C++編譯器把const修飾的變量真正變成了一個常量。