您好,登錄后才能下訂單哦!
C++是基于C語言擴展發展而來的面向對象的程序設計語言,本文將主要討論C++語言基于C語言擴展的方面。
C語言中變量的定義必須在作用域開始的位置進行定義。
#include <stdio.h>
int main(int argc, char *argv[])
{
int i;//定義變量
int j;
//使用變量
for(i = 0; i < 10; i++)
{
for(j = 0; j < 10; j++)
{
}
}
//error: 'for' loop initial declarations are only allowed in C99 mode
for(int k = 0; k < 10; k++)
{
}
return 0;
}
C++更強調語言的實用性,C++中所有的變量可以在需要使用時再定義。
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
int a = 7;
//使用時定義變量
for(int i = 0; i < 10; i++)
{
for(int j = 0; j < 10; j++)
{
}
}
return 0;
}
在C語言中:
const int a = 100;
int *p = &a;
在C++語言中:
const int a = 100;//必須在定義的時候初始化
const int *p = &a;//類型必須嚴格匹配
在C++語言中不能隱式轉換數據類型。error: invalid conversion from 'const int*' to 'int*' [-fpermissive]
C語言中,沒有定義bool類型,表示真用非0,假用0。
C++語言中,定義了自己的bool類型,真為true,假為false,是基本類型。sizeof(bool) = 1
C++編譯器會將非0值轉換為true,0轉換為false
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
bool a = 0;
printf("a = %d\n", a);//0
bool b = -1;
printf("b = %d\n", b);//1
b = b + 1;//1 + 1 = 2 => 1
printf("b = %d\n", b);//1
printf("sizeof(bool) = %d\n", sizeof(bool));//1
return 0;
}
C語言中枚舉本質就是整型,枚舉變量可以用任意整型賦值。而c++中枚舉變量,只能用被枚舉出來的元素初始化。
在C語言中,枚舉的使用
#include <stdio.h>
enum weekday
{
monday,tuesday,wednesday,thursday,friday,saturday,sunday
};
int main(int argc, char **argv)
{
enum weekday day = monday;
enum weekday a = sunday;
enum weekday b = 100;
//weekday c = sunday;錯誤用法,需要使用enum聲明
printf("%d %d %d\n", day, a, b);
return 0;
}
在C++語言中枚舉的使用
enum weekday
{
monday,tuesday,wednesday,thursday,friday,saturday,sunday
};
int main()
{
weekday day = sunday;
enum weekday a = monday;
cout<<day<<" "<<a<<endl;
return 0;
}
C語言中表達式不可以做左值,C++中某些表達式可以做左值。(a = b) = 6;
register關鍵字請求編譯器將局部變量存儲到寄存器中。
C語言中,無法獲取register關鍵字修飾的局部變量的地址。
C++語言中,C++依然支持register關鍵字,但C++編譯器對register關鍵字進行了優化,C++語言中可以獲取register變量的地址。對于早期的C++編譯器,C++編譯器發現程序中需要獲取register關鍵字修飾的變量地址時,register關鍵字對變量的聲明失效;對于現代C++編譯器,register關鍵字的存在只是為了兼容C語言,register關鍵字本身已經無任何意義。
C語言中可以重復定義多個重名的全局變量,同名的全局變量被鏈接到全局數據區的一個地址空間。
#include <stdio.h>
//定義重名的全局變量合法
int a;
int a;
int main(int argc, char *argv[])
{
return 0;
}
C++語言不允許定義多個同名的全局變量。
#include <iostream>
using namespace std;
//定義重名的全局變量是非法的
int a;
int a;
//error: redefinition of 'int a'
int main(int argc, char *argv[])
{
return 0;
}
C語言中,struct定義了一種變量的集合,struct定義的標識符不是一種新類型。C語言中的struct內部不可以定義函數。
#include <stdio.h>
struct tag_student
{
const char* name;
int age;
};
typedef struct tag_student Student;
int main(int argc, char *argv[])
{
//合法定義
Student s;
s.name = "lee";
s.age = 30;
//非法定義
tag_student ts;//error: unknown type name 'tag_student'
//合法定義
struct tag_student sts;
sts.name = "bauer";
sts.age = 23;
return 0;
}
C語言中只有使用typedef關鍵字重命名struct后才可以使用Student定義變量。
C++語言中struct用于定義一種全新的類型,可以使用struct定義的標識符直接定義變量。
#include <iostream>
using namespace std;
struct Student
{
const char* name;
int age;
};
int main(int argc, char *argv[])
{
Student s;
s.name = "bauer";
s.age = 20;
return 0;
}
在C語言中,函數在定義時沒有給出參數、返回值的類型,默認為int。
int f()表示返回值為int,接受任意參數的函數
f(void)表示返回值為int的無參數函數
#include <stdio.h>
//接受任意個參數,返回int類型
func1()
{
return 30;
}
//無參函數,返回int類型
func2(void)
{
return 20;
}
//參數i默認為int類型
void func3(i)
{
printf("i = %d\n", i);
}
int main(int argc, char *argv[])
{
int a = func1(1,2,3);
printf("a = %d\n", a);//30
int b = func2();
printf("b = %d\n", b);//20
func3(10);//10
return 0;
}
C++語言中,所有的標識符都必須顯示的聲明類型。C語言中的默認類型在C++中是不合法的。
#include <iostream>
using namespace std;
//error: ISO C++ forbids declaration of 'func1' with no type [-fpermissive]
func1()
{
return 30;
}
//無參數,返回int
int func2()
{
return 20;
}
int func3(void)
{
return 10;
}
int main(int argc, char *argv[])
{
//error: too many arguments to function 'int func2()'
int a = func2(20);
int b = func2();
int c = func3();
return 0;
}
C語言中,const修飾的變量是只讀的,本質還是變量,可以借助指針修改變量空間的值;const修飾的變量會分配存儲空間,const修飾的局部變量分配在棧上,const修飾的全局變量分配在只讀存儲區,修改const修飾的全局變量的值將會導致異常錯誤;const只在編譯期有效,在運行期無效;const關鍵詞用于向編譯器表明const修飾的變量不能做左值。
#include <stdio.h>
//const全局變量,分配在只讀存儲區
const int number = 10;
int main(int argc, char *argv[])
{
const int c = 0;
int* p = (int*)&c;//編譯器會為c分配空間
printf("Begin...\n");
*p = 5;
printf("c = %d\n", c);//5
c = 10;//error: assignment of read-only variable 'c'
int* cp = &number;
*cp = 100;//程序異常
const int number = 10;
int array[number] = {0};//只讀變量,編譯時無法確定其值
//error: variable-sized object may not be initialized
printf("End...\n");
return 0;
}
C++語言中,對C語言基礎的const進行了優化處理。編譯器編譯過程中遇到const修飾的標識符時,會將const修飾的標識符放入符號表中。如果后續編譯過程中發現const修飾的標識符時,直接使用符號表中const修飾的標識符對應的值直接替換。但在以下情況下C++編譯器會給const聲明的常量分配空間:
A、const修飾的常量為全局(extern修飾),并且需要在其它文件中使用
B、使用&操作符對cosnt常量取地址
C++編譯器雖然會對const常量分配空間,但不會使用其存儲空間的值。
const常量的判別:
A、只有用字面量初始化的const常量才會進入符號表
B、使用其他變量初始化的const常量仍然是只讀變量
C、被volatile修飾的const常量不會進入符號表
const引用類型與初始化變量的類型相同時,初始化變量為只讀變量;不同時,生成一個新的只讀變量。
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
const int a = 10;
//error: invalid conversion from 'const int*' to 'int*'
int* p = &a;
int* cp = (int*)&a;
*cp = 100;
printf("a = %d\n", a);//10
printf("*cp = %d\n", *cp);//100
int b = 3;
//使用其它變量初始化的const常量是只讀變量
const int c = b;
//error: variable-sized object 'array' may not be initialized
int array[c] = {0};
//使用volatile修飾的const常量不會進入符號表
volatile const int d = 10;
//error: variable-sized object 'varray' may not be initialized
int varray[d] = {0};
return 0;
}
C++語言中const與宏定義的不同在于,const常量由編譯器處理,編譯器會對const常量進行類型檢查和作用域檢查,而宏定義由預處理器進行處理,是單純的文本替換。
#include <iostream>
using namespace std;
void func1()
{
#define NUMBER 100
const int number = 10;
printf("NUMBER = %d\n",NUMBER);
printf("number = %d\n",number);
}
void func2()
{
//宏定義沒有作用域概念,預處理時直接替換
printf("NUMBER = %d\n",NUMBER);
printf("number = %d\n",number);
//'number' was not declared in this scope
}
int main(int argc, char *argv[])
{
func1();
func2();
const int number = 10;
//編譯時使用符號表的值替換
int array[number] = {0};
return 0;
}
C語言中,三目運算符返回變量的值,三目運算符表達式不能做左值使用。
C++語言中,三目運算符可直接返回變量本身,三目運算符表達式可以作為左值使用。但是當三目運算符表達式可能返回的值中有一個是常量值,則三目運算符表達式不能作為左值使用。
C++中,當三目運算符表達式可能返回的都是變量時,返回的是變量的引用;當三目運算符表達式可能返回的有常量值時,返回的是值。
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
int a = 3;
int b = 2;
//返回變量本身,可以做左值
(a>b?a:b) = 10;
printf("a>b?a:b = %d\n",a>b?a:b);
//返回變量的值,不能做左值
(a<b?1:b) = 20;
//error: lvalue required as left operand of assignment
return 0;
}
cin和cout是C++的標準輸入流和輸出流,在頭文件 iostream 中定義。
流名 含義 隱含設備 流名 含義 隱含設備
cin 標準輸入 鍵盤 cerr 標準錯誤輸出 屏幕
cout 標準輸出 屏幕 clog cerr 的緩沖輸出 屏幕
int main()
{
char name[30];
int age;
cout<<"pls input name and age:"<<endl;
cin>>name;
cin>>age;
cout<<"your name is: "<<name<<endl;
cout<<"your age is: "<<age<<endl;
return 0;
}
A、按進制輸出數據類型
cout<<dec<<i<<endl;
cout<<hex<<i<<endl;
cout<<oct<<i<<endl;
B、設置域寬,設置左右對齊及填充字符
int main()
{
cout<<setw(10)<<1234<<endl;
cout<<setw(10)<<setfill('0')<<1234<<endl;
cout<<setw(10)<<setfill('0')<<setiosflags(ios::left)<<1234<<endl;
cout<<setw(10)<<setfill('-')<<setiosflags(ios::right)<<1234<<endl;
return 0;
}
C、實型數據的設置
cout<<setw(5)<<'a'<<endl<<setw(5)<<100<<endl
<<setprecision(2)<<setiosflags(ios::fixed)<<120.00<<endl;
C語言中不允許重名函數的存在。
C++語言中為了簡化編程允許重名函數的存在,即使用同一個函數名定義不同的函數,重名函數稱為函數重載。
重載函數本質是定義的相互獨立的不同函數。當函數名和不同的參數搭配時函數的含義不同。
int abs(int a)
{
return a>0? a:-a;
}
double abs(double a)
{
return a>0? a:-a;
}
函數重載的規則如下:
A、函數名相同。
B、參數個數不同,參數的類型不同,參數順序不同,均可構成重載。
C、返回值類型不同則不可以構成重載。
#include <iostream>
using namespace std;
int func(int a, int b, int c = 0)
{
return a + b + c;
}
int func(int a, int b)
{
return a + b;
}
int main(int argc, char *argv[])
{
//函數調用時出現二義性
int x = func(1,2);
//error: call of overloaded 'func(int, int)' is ambiguous
return 0;
}
編譯器調用重載函數的匹配規則如下:
A、將所有同名函數作為候選者
B、尋找可行的候選參數
C、匹配成功或失敗
函數重載的匹配規則如下:
A、精確匹配實參,找到則調用。
B、通過默認參數能夠匹配實參
C、通過默認類型轉換匹配實參
通過默認類型轉換匹配實參時,通過隱式轉換尋求一個匹配,找到則調用。
C++允許int到long和double的隱式類型轉換,因此在函數重載時會引起二義性,解決方法是在調用時強轉類型。
#include <iostream>
using namespace std;
int func(int a, int b)
{
return a + b;
}
double func(double a, double b,double c)
{
cout << "func(double a, double b,double c)"<<endl;
return a + b + c;
}
long func(long a, long b,long c)
{
cout << "func(long a, long b,long c)"<<endl;
return a + b + c;
}
int main(int argc, char *argv[])
{
int a = 1;
int b = 2;
int c = 3;
//int xa = func(a,b,c);//出現二義性
int x = func((long)a,(long)b,(long)c);
printf("x = %d\n", x);
return 0;
}
編譯器調用重載函數匹配失敗的規則:
A、如果最終找到的候選函數不唯一,則出現二義性,編譯報錯。
B、如果無法匹配所有候選者,函數未定義,編譯報錯。
重載函數使用默認參數可能會造成二義性。
#include <iostream>
using namespace std;
int func(int a, int b, int c = 0)
{
return a + b + c;
}
int func(int a, int b)
{
return a + b;
}
int main(int argc, char *argv[])
{
//函數調用時出現二義性
int x = func(1,2);
//error: call of overloaded 'func(int, int)' is ambiguous
return 0;
}
C++利用name mangling(傾軋)技術,來改名函數名,區分參數不同的同名函數。
C++的name mangling實現使用 v c i f l d表示void char int float long double及其引用。
void func(char a); // func_c(char a)
void func(char a, int b, double c);//func_cid(char a, int b, double c);
name mangling發生在兩個階段,.cpp編譯階段和.h的聲明階段。只有兩個階段同時進行,才能匹配調用。
#include <iostream>
using namespace std;
//函數類型:int(int,int)
int func(int a, int b)
{
return a + b;
}
//函數類型:double(double, double, double)
double func(double a, double b,double c)
{
cout << "func(double a, double b,double c)"<<endl;
return a + b + c;
}
//函數類型:long(long,long,long)
long func(long a, long b,long c)
{
cout << "func(long a, long b,long c)"<<endl;
return a + b + c;
}
int main(int argc, char *argv[])
{
int a = 1;
int b = 2;
int c = 3;
//int func(int a, int b)
printf("x1 = 0x%X\n", (int(*)(int,int))func);
//long func(long a, long b,long c)
printf("x2 = 0x%X\n", (long(*)(long,long,long))func);
//double func(double a, double b,double c)
printf("x3 = 0x%X\n", (double(*)(double,double,double))func);
return 0;
}
使用nm工具查看main.o文件中符號表信息的命令如下:nm.exe -a main.o
func重載函數的符號表信息如下:
00000036 T __Z4funcddd
00000029 T __Z4funcii
00000090 T __Z4funclll
上述的信息表示代碼中的三個重載函數。
將重載函數名賦值給函數指針時,根據重載規則選擇與函數指針參數列表一致的函數。重載函數的函數類型與函數指針類型必須嚴格匹配(不能有任何類型的隱式轉換),此時函數返回類型將參與函數類型匹配。
函數重載必須發生在同一個作用域,無法通過函數名得到重載函數的入口地址。
重載函數的函數類型不同。
#include <iostream>
using namespace std;
//函數類型:int(int,int)
typedef int(*pFunc1)(int,int);
int func(int a, int b)
{
cout << "func(int a, int b)"<<endl;
return a + b;
}
//函數類型:double(double, double, double)
typedef double(*pFunc2)(double,double,double);
double func(double a, double b,double c)
{
cout << "func(double a, double b,double c)"<<endl;
return a + b + c;
}
//函數類型:long(long,long,long)
typedef long(*pFunc3)(long,long,long);
long func(long a, long b,long c)
{
cout << "func(long a, long b,long c)"<<endl;
return a + b + c;
}
int main(int argc, char *argv[])
{
int a = 1;
int b = 2;
int c = 3;
pFunc1 func1 = func;
int x1 = func1(1,2);//int func(int a, int b)
printf("func1 = %d\n", x1);
pFunc2 func2 = func;
int x2 = func2(1,2,3);//double func(double a, double b,double c)
printf("func2 = %d\n", x2);
pFunc3 func3 = func;
int x3 = func3(1,2,3);//long func(long a, long b,long c)
printf("func3 = %d\n", x3);
return 0;
}
重載函數的調用可能會存在隱式類型轉換,比如int到long、double類型的轉換,但是要函數指針調用重載函數時,函數指針的類型必須與重載函數的類型嚴格匹配。
函數重載的注意事項如下:
A、函數重載必然發生在同一個作用域中。
B、編譯器需要使用參數列表或函數類型進行函數的選擇。
C、不能直接通過函數名得到重載函數的入口地址。
C++完全兼容C語言,因此必須完全兼容C的類庫。由于.c文件的類庫文件中函數名并沒有發生name manling行為,而在包含.c文件所對應的.h文件時,.h 文件要發生name manling行為,因而會在編譯鏈接時候發生錯誤。
C++為了避免上述錯誤的發生,重載了關鍵字extern。只需要要避免name manling的函數前,加extern "C"如有多個,則extern "C"{}。
C語言標準庫中實際上對C++語言程序引用時做了特殊處理,在C++語言編譯器編譯時使用extern "C"將C語言的標準庫函數排除了命名傾軋。
為了確保無論在C、C++編譯器中C代碼以C語言方式編譯:
#ifdef __cplusplus
extern "C"{
#endif
//c-style code
#ifdef __cplusplus
}
#endif
C++編譯器不能以C語言方式編譯重載函數,C++編譯器將函數名和參數列表編譯為目標名,C語言編譯方式只將函數名作為目標名進行編譯。
C++調用C語言編碼的.dll時,當包含.dll的頭文件或聲明接口函數時需要加extern “C”。
add.h源碼:
#ifndef ADD_H
#define ADD_H
extern int add(int a, int b);
#endif
add.c源碼:
#include "add.h"
int add(int a, int b)
{
return a + b;
}
main.cpp源碼:
#include <stdio.h>
extern "C"
{
#include "add.h"
}
int main()
{
int c = add(10, 100);
printf("%d\n", c);
return 0;
}
gcc add.c -o add.o
g++ add.o main.cpp
C代碼中引用C++的函數和變量時,C++頭文件需要添加extern “C”,但在C代碼中不能直接引用聲明了extern “C”的C++頭文件,C代碼中只需要將C++中定義的extern “C”函數聲明為extern類型即可。
add.h源碼:
#ifndef ADD_H
#define ADD_H
extern "C" int add(int a, int b);
#endif
add.cpp源碼:
#include "add.h"
int add(int a, int b)
{
return a + b;
}
main.c源碼:
#include <stdio.h>
//#include "add.h" 錯誤
extern int add(int a, int b);
int main()
{
int x = add(1, 2);
printf("x = %d\n", x);
return 0;
}
編譯:
g++ -c add.cpp -o add.o
gcc main.c add.o -lstdc++
或是gcc add.cpp main.c -lstdc++
g++會自動進行C++標準庫的連接;用gcc連接C++程序也可以,但需要人為指定連接C++標準庫(-lstdc++),否則就會出現undefined reference to __gxx_personality_v/0
之類的錯誤。
C++提供了運算符重載機制。可以為自定義數據類型重載運算符。實現構造數據類型也可以像基本數據類型一樣的運算特性。
struct COMP
{
float real;
float image;
};
COMP operator+(COMP one, COMP another)
{
one.real += another.real;
one.image += another.image;
return one;
}
int main()
{
COMP c1 = {1,2};
COMP c2 = {3,4};
COMP sum = operator+(c1,c2); //c1+c2;
cout<<sum.real<<" "<<sum.image<<endl;
return 0;
}
實例代碼重載了一個全局的操作符+號用于實現將兩個自定義結構體類型相加。本質是函數的調用。
C++語言中,可以在函數聲明時為參數提供一個默認值。當函數調用沒有提供參數的值時,使用默認值。
函數默認參數的規則如下:
A、默認參數的順序,是從右向左,不能跳躍。
B、定義在前,調用在后(此時定義和聲明為一體),默認參數在定義處;聲明在前,調用在后,默認參數在聲明處。
C、一個函數,不能既作重載,又作默認參數的函數。當你少寫一個參數時,系統無法確認是重載還是默認參數。
函數調用時參數從左到右匹配,如果一個參數使用了默認值,則后續參數必須使用默認值。
A、單個參數
#include <iostream>
#include <time.h>
using namespace std;
void weatherForcast(char * w="sunny")
{
time_t t = time(0);
char tmp[64];
strftime( tmp, sizeof(tmp), "%Y/%m/%d %X %A ",localtime(&t) );
cout<<tmp<< "today is weahter "<<w<<endl;
}
int main(int argc, char *argv[])
{
//sunny windy cloudy foggy rainy
weatherForcast();
weatherForcast("rainny");
weatherForcast();
return 0;
}
B、多個參數
#include <iostream>
using namespace std;
float volume(float length, float weight = 4,float high = 5)
{
return length*weight*high;
}
int main(int argc, char *argv[])
{
float v = volume(10);
float v1 = volume(10,20);
float v2 = volume(10,20,30);
cout<<v<<endl;
cout<<v1<<endl;
cout<<v2<<endl;
return 0;
}
C++語言中可以為函數提供占位參數,占位參數只有類型聲明,沒有參數名聲明。由于C++類型檢查較為嚴格,為兼容C語言,可以將函數參數默認值和占位參數結合使用。
C語言中func函數如下:
#include <stdio.h>
void func()
{
}
int main(int argc, char *argv[])
{
func(5,10);
return 0;
}
C語言中func函數接收任意個數的參數。
C++語言中對func函數增加占位參數可以使C語言中的func函數快速地滿足C++語言的語法要求,代碼如下:
#include <iostream>
using namespace std;
void func(int x, int = 0)
{
}
int main(int argc, char *argv[])
{
func(5,10);
return 0;
}
變量名,本身是一段內存的引用,即別名(alias)。引用,是為己有變量起一個別名。Type& name = var;
int a;
int &b = a;
普通引用在定義時必須使用同類型的變量進行初始化。
A、引用沒有定義,是一種關系型聲明。聲明它和原有某一變量(實體)的關系。故 而類型與原類型保持一致,且不分配內存,與被引用的變量有相同的地址。
B、聲明的時候必須初始化,一經聲明,不可變更。
C、可對引用再次引用。多次引用的結果,是某一變量具有多個別名。
D、&符號前有數據類型時,是引用。其它皆為取地址。
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
int a = 3;
int c = 6;
float f = 3.14;
int& b = a;
printf("&a = 0x%X\n", &a);
printf("&b = 0x%X\n", &b);
//error: redeclaration of 'int& b'
int& b = c;//error
//給b賦值
b = c;
//引用的類型必須與變量類型相同
int& d = f;//error
//error: invalid initialization of reference of type 'int&' from expression of type 'float'
//引用在定義時必須初始化
int& rd;//error
//error: 'rd' declared as reference but not initialized
//引用不可以使用字面值初始化
int& r = 10;//error
//error: invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int'
return 0;
}
函數中的引用形參不需要進行初始化,函數調用時進行初始化。
#include <iostream>
using namespace std;
//引用
void swap(int &a, int &b)
{
int tmp;
tmp = a;
a = b;
b = tmp;
printf("swap(int &a, int &b)\n");
}
//指針
void swap(int* a, int* b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
printf("swap(int* a, int* b)\n");
}
int main(int argc, char *argv[])
{
int a = 3;
int b = 6;
swap(a,b);
printf("a = %d\n",a);//6
printf("b = %d\n",b);//3
swap(&a,&b);
printf("a = %d\n",a);//3
printf("b = %d\n",b);//6
return 0;
}
4、引用的提高
A、可以定義指針的引用,但不能定義引用的引用。
int a;
int* p = &a;
int*& rp = p; // ok
int& r = a;
int&& rr = r; // error
B、可以定義指針的指針(二級指針),但不能定義引用的指針。
int a;
int* p = &a;
int** pp = &p; // ok
int& r = a;
int&* pr = &r; // error
C、可以定義指針數組,但不能定義引用數組,可以定義數組引用。
int a, b, c;
int* parr[] = {&a, &b, &c}; // ok
int& rarr[] = {a, b, c}; // error
int arr[] = {1, 2, 3};
int (&rarr)[3] = arr; // ok 的
數組是連續的存儲空間,數組中的元素如果是引用,會導致數組的元素存儲不連續。引用數組會破壞數組存儲空間的連續性。
const引用所引用的對象必須是const的,將普通引用綁定到const引用對象是不合法的。 const type& name = var;
const引用可使用相關類型的對象(常量,非同類型的變量或表達式)初始化,const引用讓變量具有只讀屬性,是const引用與普通引用最大的區別。
非const引用只能綁定到與該引用同類型的對象。
當const引用使用字面常量值初始化時,C++編譯器會為常量值分配空間,使用字面常量對const引用初始化將生成一個只讀變量。
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
int a = 10;
const int& ra = a;//const引用,為只讀變量
//只讀變量不能作為左值
ra = 100;//error: assignment of read-only reference 'c'
int* p = (int*)&ra;
*p = 5;
printf("ra = %d\n", ra);//5
const int& rb = 10;//rb為只讀變量,占用內存空間
//只讀變量不能作為左值
rb = 100;//error
int arraya[ra] = {0};//error
//error: variable-sized object 'arraya' may not be initialized
int arrayb[rb] = {0};//error
//error: variable-sized object 'arrayb' may not be initialized
double pi = 3.14;
int& rpi = pi;//非法
//error: invalid initialization of reference of type 'int&' from expression of type 'double'
const int& crpi = pi;//合法
printf("rpi= %d\n",crpi);//3
return 0;
}
C++編譯器在編譯過程中使用指針常量作為引用的內部實現,因此引用所占用空間大小與指針相同。引用的本質是一個指針常量。type & name<====> type * const name;
使用引用時不能返回局部變量的引用
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
printf("sizeof(char&) = %d\n", sizeof(char&));//1
char c = 'a';
char& rc = c;
printf("sizeof(char&) = %d\n", sizeof(rc));//1
return 0;
}
上述代碼的匯編代碼如下:
將字符’a’(0x61)存儲到指針寄存器0x1b,將0x1b放入eax數據寄存器中,再將eax數據寄存器存儲內容放入指針寄存器0x1c中。
C語言中提供了malloc和free兩個系統函數,完成對堆內存的申請和釋放。而C++則提供了兩關鍵字new和delete。
new分配內存空間時,不能保證按需分配,分配內存空間大小可能會大于所需空間大小。因此,new會分配至少申請大小的內存空間。
A、開辟單變量地址空間
int *p = new int; //開辟大小至少為sizeof(int)空間
int *a = new int(5); //開辟大小至少為sizeof(int)空間,并初始化為 5
B、開辟數組空間
一維: int a = new int[100];//開辟一個大小不少于400字節的整型數組空間
二維: int (a)[6] = new int[5][6]
三維: int (*a)[5][6] = new int[3][5][6]
A、釋放單變量空間
int *a = new int;
delete a; //釋放單個 int 的空間
B、釋放數組空間
int *a = new int[5];
delete []a; //釋放 int 數組空間
int *p = new int(5);
cout<<*p<<endl;
delete p;
char *pp = new char[10];
strcpy(pp,"china");
cout<<pp<<endl;
delete []pp;
string *ps = new string("china");
cout<<*ps<<endl; //cout<<ps<<endl;
delete ps;
char **pa= new char*[5];
memset(pa,0,sizeof(char*[5]));
pa[0] = "china";
pa[1] = "america";
char **pt = pa;
while(*pt)
{
cout<<*pt++<<endl;
}
delete []pt;
int (*qq)[3][4] = new int[2][3][4];
delete []qq;
//C++ 內存申請失敗會拋出異常
try{
int *p = new int[10];
}
catch(const std::bad_alloc e)
{
return -1;
}
//C++ 內存申請失敗不拋出異常版本
int *q = new (std::nothrow)int[10];
if(q == NULL)
return -1;
C++中堆空間的分配和釋放注意事項如下:
A、new/delete 是關鍵字,效率高于 malloc 和 free.
B、配對使用,避免內存泄漏和多重釋放。
C、避免交叉使用。比如 malloc 申請的空間去 delete,new 出的空間被 free;
D、重點用在類對像的申請與釋放。申請的時候會調用構造器完成初始化,
釋放的時候,會調用析構器完成內存的清理。
malloc與new的區別如下:
A、new是C++關鍵字,malloc是C語言庫函數
B、new以具體類型為單位進行內存分配,malloc以字節位單位分配內存
C、new在申請單個類型變量時可以進行初始化,malloc不具備
D、new在所有C++編譯器中都支持,malloc在某些系統開發中不可調用
E、new能夠觸發構造函數的調用,malloc僅分配需要的內存空間
F、對象的創建只能使用new,malloc不適合面向對象開發
free與delete的區別如下:
A、delete是C++關鍵字,free是庫函數
B、delete在所有C++編譯器中都支持,free在某些系統開發中不可調用
C、delete能夠觸發析構函數的調用,free僅歸還分配的內存空間
D、對象的銷毀只能使用delete,free不適合面向對象開發
E、free可以歸還new申請的內存空間,但不會調用析構函數,可能會造成內存泄漏
F、delete可以釋放malloc分配的內存空間,但會調用析構函數,會造成其他問題。
C語言中有宏函數的概念。宏函數的特點是內嵌到調用代碼中去,避免了函數調用的開銷。但宏函數的處理發生在預處理階段,缺少作用域檢查和類型檢查。
C++提供了inline關鍵字,請求C++編譯器將一個函數進行內聯編譯(C++編譯器可以拒絕),C++編譯器會直接將內聯的函數體代碼插入函數調用的地方。
內聯函數聲明時inline關鍵字必須和函數定義結合在一起,否則編譯器會直接忽略內聯請求。
inline int sqr(int x)
{
return x*x;
}
內聯函數的優點:避免調用時的額外開銷(入棧與出棧操作)
內聯函數的缺點:內聯函數的函數體在代碼段中會出現多個“副本”,因此會增加代碼段的空間。
內聯函數的本質:以犧牲代碼段空間為代價,提高程序的運行時間的效率。
內聯函數的適用場景:函數體很“小”,且被“頻繁”調用。
Inline關鍵字是對編譯器的建議,如果編譯器認為inline聲明的函數可以內聯,則編譯器會將函數內聯,如果編譯器認為inline聲明的函數的函數體太長,則不會內聯,按普通函數處理。
using namespace std;
inline int func(int a, int b)
{
return a*a + b*b;
}
int main(int argc, char *argv[])
{
int a = 3;
int b = 4;
int c = func(a,b);
return 0;
}
上述代碼在QtCreator+MinGW編譯器下調試時,查看匯編代碼如下:
調用func函數時,C++編譯器沒有將func函數內聯,仍然使用函數調用。
inline聲明的函數,內聯請求可能被C++編譯器拒絕。
現代C++編譯器能夠進行編譯優化,一些函數即使沒有inline關鍵字聲明也能夠內聯編譯,同時現代C++編譯器提供了擴展的語法,能夠對函數進行強制內聯。如現代G++編譯器使用__attribute__((always_inline))
聲明強制內聯,MSVC編譯器使用__forceinline
聲明強制內聯,不再使用inline關鍵字。
#include <iostream>
using namespace std;
__attribute__((always_inline))
int func(int a, int b);
int main(int argc, char *argv[])
{
int a = 3;
int b = 4;
int c = func(a,b);
return 0;
}
int func(int a, int b)
{
return a*a + b*b;
}
上述代碼在QtCreator+MinGW編譯器中進行調試時,main函數中func函數調用代碼的匯編代碼如下:
MinGW編譯器已經對func函數進行了內聯。
C++中使用inline關鍵字內聯編譯函數的限制:
A、不能存在任何形式的循環語句
B、不能存在過多的條件判斷語句
C、函數體不能過于龐大
D、不能對函數進行取地址操作
E、函數內聯聲明必須在調用語句前
對于現代C++編譯器的擴展語法提供的強制內聯不受上述條件限制。
C語言中的類型轉換是強制轉換,任何類型間都可以轉換,過于粗暴。
C++語言引入了static_cast、dynamic_cast、const_cast、reinterpret_cast四個關鍵字處理不同類型間的轉換。
靜態類型轉換是在編譯期內即可決定其類型的轉換。
靜態類型轉換的使用場合:
A、用于基本類型間的轉換
B、不能用于基本類型指針間的轉換
C、用于有繼承關系類對象間的轉換和類指針間的轉換(轉換一般從子對象向父對象轉換)
語法格式:
static_cast<目標類型> (標識符)
應用實例:
#include <stdio.h>
class A
{
private:
int a;
int b;
public:
A()
{
a = 0;
b = 0;
}
void print()
{
printf("a = %d, b = %d\n", a, b);
}
};
class B : public A
{
private:
int c;
int d;
public:
void display()
{
printf("c = %d, d = %d\n", c, d);
}
};
class C
{
public:
void print()
{
printf("hello\n");
}
};
int main()
{
float f = static_cast<float>(9)/10;//基本類型的轉換
printf("f = %f\n", f);
A a;
B b;
A aa = static_cast<A>(b);//將子類對象轉換為父類對象
aa.print();
//B bb = static_cast<B>(a);//不能將父對象轉換為子對象
A* pa = static_cast<A*>(&a);//在同類型對象指針間轉換
pa->print();
pa = static_cast<A*>(&b);//將子類對象指針轉換為父類對象指針
pa->print();
B* pb = static_cast<B*>(&b);
pb->display();
pb = static_cast<B*>(&a);
pb->display();
//C c = static_cast<C>(a);//沒有轉換構函數,不能將A類型轉換為C類型
//c.print();
return 0;
}
動態類型轉換的使用場合:
A、用于有繼承關系的類指針間的轉換
B、有交叉關系的類指針間的轉換
C、具有類型檢查
D、必須有虛函數支持
語法格式:
dynamic_cast<目標類型> (標識符)
用于有直接或間接繼承關系的指針(引用)的強制轉換
轉換指針成功將會得到目標類型的指針,轉換失敗將得到一個空指針;
轉換引用成功將得到目標類型的引用,轉換失敗將得到一個異常操作信息。
使用實例:
#include <iostream>
#include <string>
using namespace std;
class Base
{
public:
Base()
{
cout << "Base::Base()" << endl;
}
virtual ~Base()
{
cout << "Base::~Base()" << endl;
}
};
class Derived : public Base
{
};
int main()
{
Base* p = new Derived;
Derived* pd = dynamic_cast<Derived*>(p);//將指向子類對象的父類指針轉換為子類指針,轉換成功
if( pd != NULL )
{
cout << "pd = " << pd << endl;
}
else
{
cout << "Cast error!" << endl;
}
delete p;
cout << endl;
p = new Base;
pd = dynamic_cast<Derived*>(p);//將指向父類對象的父類指針轉換為子類指針,轉換失敗
if( pd != NULL )
{
cout << "pd = " << pd << endl;
}
else
{
cout << "Cast error!" << endl;
}
delete p;
return 0;
}
常量類型轉換的使用場合:
A、用于去除變量的只讀屬性
B、目標類類型只能是指針或引用
語法格式:
const_cast<目標類型> (標識符) //目標類類型只能是指針或引用。
const_cast將轉換掉表達式的const屬性
應用實例:
#include <iostream>
using namespace std;
int main()
{
const int& a = 10;
int& b = const_cast<int&>(a);//將a的只讀屬性去除,并初始化b
b = 0;
cout << "a = " << a << endl;//0
cout << "b = " << b << endl;//0
const int c = 100;
int& d = const_cast<int&>(c);//為c分配一個只讀空間
//int x = const_cast<int>(c);//error,目標類型只能為指針和引用
d = 1000;
cout << "c = " << c << endl;//100
cout << "d = " << d << endl;//1000
return 0;
}
重解釋類型轉換使用場合:
A、用于指針類型間的強制轉換
B、用于整數和指針類型間的強制轉換
語法格式:
reinterpret_cast<目標類型> (標識符)
為數據的二進制形式重新解釋,但是不改變其值。
使用實例:
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
int a = 100;
char c = 'a';
int* pa = reinterpret_cast<int*>(&c);
printf("*pa = %d\n", *pa);//687781729
printf("*pa = %c\n", *pa);//'a'
//int x = reinterpret_cast<int>(c);//error
//int y = reinterpret_cast<int>(1.1);//error
return 0;
}
C語言中,只有一個全局作用域,所有的全局標識符共享一個作用域,因此標識符之間可能存在沖突。
C++語言中,提出了命名空間的概念。命名空間將全局作用域分為不同的部分,不同命令空間中的標識符可以重名而不會發生沖突,命名空間可以嵌套。全局作用域即默認命名空間。
global scope是一個程序中最大的scope,是引起命名沖突的根源。C語言沒有從語言層面提供命名空間機制來解決。global scope是無名的命名空間。
NameSpace是對全局區域的再次劃分。
命名空間的聲明如下:
namespace NAMESPACE
{
全局變量 int a;
數據類型 struct Stu{};
函數 void func();
}
直接指定命名空間: NameSpace::a = 5;
使用using+命名空間+空間元素:using NameSpace::a; a = 2000;
使用using +namespace+命名空間;
#include <iostream>
using namespace std;
namespace MySpace
{
int x = 1;
int y = 2;
}
namespace Other {
int x = 3;
int y = 4;
}
int main()
{
{
using namespace MySpace;
cout<<x<<y<<endl;
}
{
using namespace Other;
cout<<x<<y<<endl;
}
{
MySpace::x = 100;
Other::y = 200;
cout<<MySpace::x<<Other::y<<endl;
}
return 0;
}
可以使用塊語句將命名空間限定在塊語句內部。
namespace MySpace
{
int x = 1;
int y = 2;
namespace Other {
int m = 3;
int n = 4;
}
}
在實際項目開發中,可以將一個類或者具有相同屬性的多個類聲明在一個命名空間內,在使用時只需要聲明命名空間即可。
#ifndef A_H
#define A_H
namespace XX {
class A
{
public:
A();
~A();
};
}
#endif // A_H
#include "a.h"
using namespace XXX
{
A::A()
{}
A::~A()
{}
}
除了使用字符數組來處理字符串以外,C++引入了字符串類型。可以定義字符串變量。
string str;
str = "china";
string str2 = " is great ";
string str3 = str2;
string str = "china";
cout << sizeof(str) << " " << str.max_size() << " " << str.size()<<endl;
4 1073741820 5
A、賦值string str3 = str2;
B、加法string combine = str + str2;
C、關系
string s1 = "abcdeg";
string s2 = "12345";
if(s1>s2)
cout<<"s1>s2"<<endl;
else
cout<<"s1<s2"<<endl;
string數組是高效的,如果用二維數組來存入字符串數組的話,則容易浪費空間,此時列數是由最長的字符串決定。如果用二級指針申請堆空間,依據大小申請相應的空間,雖然解決了內存浪費的問題,但是操作麻煩。用 string 數組存儲,字符串數組的話,效率即高又靈活。
string sArray[10] = {
"0",
"1",
"22",
"333",
"4444",
"55555",
"666666",
"7777777",
"88888888",
"999999999",
};
for(int i=0; i<10; i++)
{
cout<<sArray[i]<<endl;
}
int capacity()const;??//返回當前容量(即string中不必增加內存即可存放的元素個數)
int max_size()const;??? //返回string對象中可存放的最大字符串的長度
int size()const;??????? //返回當前字符串的大小
int length()const;?????? //返回當前字符串的長度
bool empty()const;??????? //當前字符串是否為空
void resize(int len,char c);//把字符串當前大小置為len,并用字符c填充不足的部分
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。