亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

C++教程(超長最全入門)

 更新時間:2023年05月05日 00:02:41   作者:嗨,人  
這篇文章主要介紹了C++教程(超長最全),需要的朋友可以參考下

1、C++簡介

1.1 起源

-貝爾實(shí)驗(yàn)室20世紀(jì)80年代(1979)

1.2 應(yīng)用范圍

  • 文字處理程序以及電子表格
  • 編譯器
  • 操作系統(tǒng)
  • 大型游戲等

1.3 C++和C

  • C語言是結(jié)構(gòu)化和模塊化的語言,面向過程。
  • C++保留了C語言原有的所有優(yōu)點(diǎn),增加了面向?qū)ο蟮臋C(jī)制,俗稱“帶類的C",1983年更名為C++

2、開發(fā)工具

  • 記事本(Notepad++)+命令行
  • Visual C++ 6.0:經(jīng)典開發(fā)工具,與流行操作系統(tǒng)有沖突
  • VS 2015等:功能強(qiáng)大,體積也強(qiáng)大
  • Code::Blocks:開源免費(fèi)開發(fā)工具,專業(yè)開發(fā)人員推薦使用
  • 其他開發(fā)工具:DeV C++、CLion、C-Free、Xcode、C4droid

3、基本語法

  • 對象-對象具有狀態(tài)的行為。對象是類的實(shí)例。
  • 類-類可以定義為對象行為、狀態(tài)的模版。
  • 方法-從基本上講,一個方法表示一種行為,一個類可以包含多種方法。
  • 變量

3.1 注釋

//單行注釋
/*
多行注釋
多行注釋
*/

3.2關(guān)鍵字

asmelsenewthis
autoenumoperatorthrow
boolexplicitprivatetrue
breakexportprotectedtry
caseexternpublictypedef
catchfalseregistertypeid
charfloatreinterpret_casttypename
classforreturnunion
constfriendshortunsigned
const_castgotosignedusing
continueifsizeofvirtual
defaultinlinestaticvoid
deleteintstatic_castvolatile
dolongstructwchar_t
doublemutableswitchwhile
dynamic_castnamespacetemplate

3.3標(biāo)識符

  • 標(biāo)識符是用來標(biāo)識變量、函數(shù)、類、模塊,或任何其他用戶自定義項(xiàng)目的名稱。一個標(biāo)識符以字母 A-Z 或 a-z 或下劃線 _ 開始,后跟零個或多個字母、下劃線和數(shù)字(0-9)。
  • 標(biāo)識符內(nèi)不允許出現(xiàn)標(biāo)點(diǎn)字符,比如 @、& 和 %。C++ 是區(qū)分大小寫的編程語言。

4、數(shù)據(jù)類型

4.1基本數(shù)據(jù)類型

七種基本的C++數(shù)據(jù)類型:bool、char、int、float、double、void、wchar_t
類型修飾符:signed、unsigned、short、long

注:一些基本類型可以使用一個或多個類型修飾符進(jìn)行修飾,比如:signed short int簡寫為short、signed long int 簡寫為long。

類型名占用字節(jié)數(shù)數(shù)值范圍
void0
bool1{true.false}
wchar_t2或4個字節(jié)
char(signed char)1-128~+127
short(signed short)2-32768~+32767
int(signed int)4-2147483648~+2147483647
long(signed long)4-2147483648~+2147483647
long long(signed long long)8-9,223,372,036,854,775,808 ~9,223,372,036,854,775,807
float4-.34*1038~3.4*1038
double8-1.7*10308~1.7*10308
unsigned char10~255
unsigned shrot20~65525
unsigned(unsigned int)40~4294967295
unsigned long40~4294967295
unsigned long long80 ~ 18,446,744,073,709,551,615
//x64處理器 64位window10 vs2015 
#include <iostream>
using namespace std;
int main()
{
	bool b;
	char c;short s; int i; long l; long long ll; float f; double d; long double ld;long float lf;
	unsigned char uc; unsigned short us; unsigned int ui; unsigned long ul; unsigned long long ull;
	cout << sizeof(bool) <<  endl;
	cout << sizeof(char)<<" " << sizeof(short)<<" "<< sizeof(signed int) << " " << sizeof(long) << " " << sizeof(signed long long) << " " << sizeof(float) << " " << sizeof(double) << " " << sizeof(long float) << " " << sizeof(long double) << endl;
	cout <<sizeof(unsigned char)<<" "<< sizeof(unsigned short) << " " << sizeof(unsigned int) << " " << sizeof(unsigned long) << " " << sizeof(unsigned long long) << endl;
	cout << sizeof(unsigned) << endl;
	cout << "hello World!!!" <<endl;
	system("pause");
	return 0;
}

輸出結(jié)果

4.2 數(shù)據(jù)類型在不同系統(tǒng)中所占空間大小

這個與機(jī)器、操作系統(tǒng)、編譯器有關(guān)。比如同樣是在32bits的操作系統(tǒng)系,VC++的編譯器下int類型為占4個字節(jié);而tuborC下則是2個字節(jié)。

原因:

  • c/c++規(guī)定int字長和機(jī)器字長相同
  • 操作系統(tǒng)字長和機(jī)器字長未必一致
  • 編譯器根據(jù)操作系統(tǒng)字長來定義int字長
類型16位操作系統(tǒng)32位操作系統(tǒng)64位操作系統(tǒng)
char111
char*248
short222
int244
long448
long long888

注:long類型在不同編譯器中的占位不一樣: 32位時,VC++和GCC都是4字節(jié); 64位時,VC++是4字節(jié),GCC是8字節(jié)。

4.3 typedef聲明

//使用typedef為一個已有的類型取一個新的名字,語法如下:
typedef type newname
//eg:
typedef int feet
feet distance

4.4 枚舉類型

C++中的一種派生數(shù)據(jù)類型,它是由用戶定義的若干枚舉常量的集合;枚舉元素是一個整型,枚舉型可以隱式的轉(zhuǎn)換為int型,int型不能隱式的轉(zhuǎn)換為枚舉型。

//枚舉類型的語法:
enum 枚舉名{
	標(biāo)識符[=整型常數(shù)], 
     標(biāo)識符[=整型常數(shù)], 
... 
    標(biāo)識符[=整型常數(shù)]
}枚舉變量;

如果枚舉沒有初始化, 即省掉"=整型常數(shù)"時, 則從第一個標(biāo)識符開始;

默認(rèn)情況下,第一個名稱的值為 0,第二個名稱的值為 1,第三個名稱的值為 2,以此類推。但是,您也可以給名稱賦予一個特殊的值,只需要添加一個初始值即可。

例如:

enum course {math,chinese,english,physics,chemistry}c;
c = english;
cout<<c<<endl;  //2
//english為1 physics為2 chemistry為3,chinese仍為1,math仍為0
enum course {math,chinese,english=1,physics,chemistry};

5、變量

變量其實(shí)只不過是程序可操作的存儲區(qū)的名稱。C++ 中每個變量都有指定的類型,類型決定了變量存儲的大小和布局,該范圍內(nèi)的值都可以存儲在內(nèi)存中,運(yùn)算符可應(yīng)用于變量上。

5.1 變量的聲明和定義

  • 變量聲明向編譯器保證變量以給定的類型和名稱存在,這樣編譯器在不需要知道變量完整細(xì)節(jié)的情況下也能繼續(xù)進(jìn)一步的編譯。
  • 可以在 C++ 程序中多次聲明一個變量,但變量只能在某個文件、函數(shù)或代碼塊中被定義一次。
  • 多個變量賦同一個值時,需要分別賦值。
int x = y = z = 66;//錯誤
int x = 3,y = 3,z = 3;
int x, y ,z = 3;
x = y = z;

變量的聲明(不分配內(nèi)存):extern 數(shù)據(jù)類型 變量名;
變量的定義:數(shù)據(jù)類型 變量名1,變量名2,...變量名n;

// 變量聲明
extern int a, b;
int main ()
{
  // 變量定義
  int a, b;
  // 初始化
  a = 23;
  b = 25;
  return 0;
}

5.2 變量的作用域

局部變量:在函數(shù)或一個代碼塊內(nèi)部聲明的變量,稱為局部變量。它們只能被函數(shù)內(nèi)部或者代碼塊內(nèi)部的語句使用。
全局變量:在所有函數(shù)外部定義的變量(通常是在程序的頭部),稱為全局變量。全局變量的值在程序的整個生命周期內(nèi)都是有效的。

  • 局部變量和全局變量的名稱可以相同,但是在函數(shù)內(nèi),局部變量的值會覆蓋全局變量的值。
  • 當(dāng)局部變量被定義時,系統(tǒng)不會對其初始化;定義全局變量時,系統(tǒng)會自動初始化值:int float double 0,char ’\0‘,指針 NULL
int i = 66;
int main ()
{
  int i = 88;
  cout << i<<endl;//8
  return 0;
}
float f;
double d;
char c;
int *p;
int main()
{
	cout << i << f << d << c << p << endl;//000 00000000
	return 0
}

6、運(yùn)算符

  • 算術(shù)運(yùn)算符:+ - * / % ++ --
  • 關(guān)系運(yùn)算符:== != < > >= <=
  • 邏輯運(yùn)算符:&& || !
  • 位運(yùn)算符:& | ^ ~ << >>
  • 賦值運(yùn)算符:= += -= *= /= %= <<= >>= &= ^= !=
  • 雜項(xiàng)運(yùn)算符:
sizeof            //返回變量的大小,eg:sizeof(a)返回4 a是整型  sizeof(int)
Condition?X:Y     //三元運(yùn)算符 Condition為true,值為X,否則值為Y
,                //逗號表達(dá)式,值為最后一個表達(dá)式的值
.和->            //用于引用類、結(jié)構(gòu)和公用體的成員
Cast            //強(qiáng)制類型轉(zhuǎn)換符  eg:int(2.202)返回2
&              //指針運(yùn)算符 返回變量的地址
*             //指針運(yùn)算符  指向一個變量

運(yùn)算符優(yōu)先級

類別運(yùn)算符結(jié)合性
后綴() [] -> . ++ - -從左到右
一元+ - ! ~ ++ - - (type)* & sizeof從右到左
乘除* / %從左到右
加減+ -從左到右
移位<< >>從左到右
關(guān)系< <= > >=從左到右
相等== !=從左到右
位與 AND&從左到右
位異或 XOR^從左到右
位或 OR|從左到右
邏輯與 AND&&從左到右
邏輯或 OR||從左到右
條件?:從右到左
賦值= += -= *= /= %=>>= <<= &= ^==
逗號,從左到右

7、語法結(jié)構(gòu)

7.1 循環(huán)結(jié)構(gòu)

while

while(conditon)//0為false,非0為true
{
	statement(s);
}

for

for(init;conditon;increment)//0為false,非0或什么也不寫為true
{
	statement(s);
}

1.init首先被執(zhí),且只會執(zhí)行一次,也可以不寫任何語句。
2.然后會判斷conditon,true執(zhí)行循環(huán)主體,false跳過循環(huán)
3.執(zhí)行完循環(huán)主體,執(zhí)行increment,跳到2

int array[5] = { 11, 22, 33, 44, 55 };
for (int x : array)
{
	cout << x << " ";
}
cout << endl;
// auto 類型也是 C++11 新標(biāo)準(zhǔn)中的,用來自動獲取變量的類型
for (auto x : array)
{
	cout << x << " ";
}

for each

STL中的for增強(qiáng)循環(huán)。

int a[4] = { 4,3,2,1 };
for each (int var in a)
{
	cout << var << " ";
}

7.2 判斷結(jié)構(gòu)

if

if(expr)
{
	statement;//如果expr為true將執(zhí)行的語句塊 
}
if(expr)
{
   statement1;// 如果expr為true將執(zhí)行的語句塊
}
else
{
   statement2;// 如果expr為false將執(zhí)行的語句
}
if(expr1)
{
   statement1;// 如果expr1為true將執(zhí)行的語句塊
}
elseif(expr2)
{
   statement2;// 如果expr2為true將執(zhí)行的語句塊
}
...
else
{
	statementElse;// 當(dāng)上面的表達(dá)式都為false執(zhí)行的語句塊
}

switch

switch(expression){
    case constant-expression  :
       statement(s);
       break; 
    case constant-expression  :
       statement(s);
       break; 
    // 您可以有任意數(shù)量的 case 語句
    default : // 可選的
       statement(s);
}
  • 每個case后滿的常量表達(dá)式必須各不相同。
  • case語句和default語句出現(xiàn)的順序?qū)?zhí)行結(jié)果沒有影響。
  • 若case后沒有break,執(zhí)行完就不會判斷,繼續(xù)執(zhí)行下一個case語句。直到遇到brerak。
  • default后面如果沒有case,則break可以省略
  • 多個case可以用一組執(zhí)行語句
char c = 'A';
	switch (c)
	{
	case 'A':
	case 'B':
	case 'C':
		cout << "及格了" << endl;
		break;
	default:
		cout << "不及格" << endl;
	}

7.3 三元運(yùn)算符

//如果 Exp1 為真,則計(jì)算 Exp2 的值,結(jié)果即為整個 ? 表達(dá)式的值。如果 Exp1 為假,則計(jì)算 Exp3 的值,結(jié)果即為整個 ? 表達(dá)式的值
Exp1 ? Exp2 : Exp3;

7.4 預(yù)處理命令

預(yù)處理程序(刪除程序注釋,執(zhí)行預(yù)處理命令等)–>編譯器編譯源程序

  • 宏定義:#define 標(biāo)識符 字符串
  • 文件包含:#include<filename> 或者#include“filename”
  • 條件編譯
//如果標(biāo)識符被#define定義過,執(zhí)行程序段1,否則執(zhí)行程序段2
#ifdef 標(biāo)識符
	程序段1
#else
	程序段2
#endif
//如果標(biāo)識符沒有被#define定義過,執(zhí)行程序段1,否則執(zhí)行程序段2
#ifndef 標(biāo)識符
	程序段1
#else
	程序段2
#endif
//如果表達(dá)式為true,執(zhí)行程序段1,否則執(zhí)行程序段2
#if 表達(dá)式
	程序段1
#else
	程序段2
#endif

8、數(shù)組

一些具有相同數(shù)據(jù)類型或相同屬性(類)的數(shù)據(jù)的集合,用數(shù)據(jù)名標(biāo)識,用下標(biāo)或序號區(qū)分各個數(shù)據(jù)。數(shù)組中的數(shù)據(jù)稱為元素。

8.1一維數(shù)組

定義一維數(shù)組的形式:數(shù)據(jù)類型 數(shù)據(jù)名[常量表達(dá)式]
初始化的形式:數(shù)據(jù)類型 數(shù)組名[常量表達(dá)式] = {初值表};
為數(shù)組的某一個元素賦值:數(shù)組名[下標(biāo)] =值(下標(biāo)從0開始)
數(shù)組的引用:數(shù)組名[下標(biāo)]

  • 初始化數(shù)組時,可以只給部分?jǐn)?shù)組元素賦值
  • 對全部元素?cái)?shù)組賦值時,可以不指定數(shù)組長度,編譯系統(tǒng)會根據(jù)初值個數(shù)確定數(shù)組的長度。
  • static型數(shù)組元素不賦初值,系統(tǒng)會自動默認(rèn)為0。
int arr1[4] = {1,2,3,4};
int arr2[4] = { 1,2 };
int arr[4] = {0];//所有元素為0
static int arr3[3];
int arr4[4];
cout << "arr1:"<<arr1[0] << arr1[1] << arr1[2] << arr1[3] << endl;
cout << "arr2:" << arr2[0] << arr2[1] << arr2[2] << arr2[3] << endl;
cout << "arr3:" << arr3[0] << arr3[1] << arr3[2] << arr3[3] << endl;
cout << "arr4:" << arr4[0] << arr4[1] << arr4[2] << arr4[3] << endl;

在這里插入圖片描述

8.2二維數(shù)組

定義一維數(shù)組的形式:數(shù)據(jù)類型 數(shù)據(jù)名[常量表達(dá)式1][常量表達(dá)式2]
初始化的形式:數(shù)據(jù)類型 數(shù)組名[常量表達(dá)式1] [常量表達(dá)式2]= {初值表};
為數(shù)組的某一個元素賦值:數(shù)組名[行下標(biāo)][列下標(biāo)] =值(下標(biāo)從0開始)
數(shù)組的引用:數(shù)組名[行下標(biāo)][列下標(biāo)]

  • 將所有數(shù)據(jù)寫在一個花括號內(nèi),自動按照數(shù)組元素個數(shù)在內(nèi)存中排列的順序賦值
  • 可對部分元素賦值,其余元素的值自動取0.
  • 定義初始化數(shù)組時,可以省略第一維的長度,第二維不能省,系統(tǒng)會自動確認(rèn)行數(shù)
int arr1[2][3];
int arr[2][3] = {0];//所有元素為0
int arr2[2][3] = { {1,2,3},{4,5,6} };
int arr3[2][3] = { 1,2,3 ,4,5,6 };
int arr4[2][3] = { {1},{4,6} };
int arr5[][3] = { 1,2,3 ,4,5,6 };

在這里插入圖片描述

字符數(shù)組

char類型的數(shù)組,在字符數(shù)組中最后一位為’\0’)時,可以看成時字符串。在C++中定義了string類,在Visual C++中定義了Cstring類。
字符串中每一個字符占用一個字節(jié),再加上最后一個空字符。

如:

//字符串長度為8個字節(jié),最后一位是'\0'。
char array[10] = "yuanrui";//yuanrui\0\0\0
//也可以不用定義字符串長度,如:
char arr[] = "yuanrui";//yuanrui\0

8.3 指向數(shù)組的指針

指針的概念會在后面詳細(xì)講解。

double *p;
double arr[10];
p = arr;//p = &arr[0];
*(p+3);//arr[3]

8.4 數(shù)組與new(動態(tài)創(chuàng)建數(shù)組)

一維數(shù)組:

int* arr1 = new int[2];//delete []arr1;
int* arr2 = new int[3]{ 1,2 };//delete []arr2

二維數(shù)組

int m=2, n=3;
int** arr3 = new int*[2];//delete []arr3
for (int i = 0; i < 10; ++i)
{ 
	arr3[i] = new int[3]; // delete []arr3[i]
}
int* arr4 = new int[m*n];//數(shù)據(jù)按行存儲 delete []arr3

8.5 數(shù)組與函數(shù)

數(shù)組->函數(shù)

如果傳遞二維數(shù)組,形參必須制定第二維的長度。

形式參數(shù)是一個指針:void function(int *param)形式參數(shù)是一個已定義大小的數(shù)組:void function(int param[10])形式參數(shù)是一個未定義大小的數(shù)組:void function(int param[])二維數(shù)組:void function(int a[][3],int size)

函數(shù)返回?cái)?shù)組

C++ 不支持在函數(shù)外返回局部變量的地址,除非定義局部變量為 static 變量。

int * function();
int** function();

8.6 獲取數(shù)組的大小

動態(tài)創(chuàng)建(new)的基本數(shù)據(jù)類型數(shù)組無法取得數(shù)組大小

int a[3];
//第一種方法
cout<<sizeof(a)/sizeof(a[0])<<endl;
//第二種方法
cout << end(a) - begin(a) << endl;
//二維數(shù)組
int arr[5][3];
int lines = sizeof(arr) / sizeof(arr[0][0]);
int row = sizeof(arr) / sizeof(arr[0]);//行
int col = lines / row;//列
cout << row << "::"<<col << endl;
cout << end(arr) - begin(arr) << endl;//5行

9、函數(shù)

函數(shù)是實(shí)現(xiàn)模塊化程序設(shè)計(jì)思想的重要工具, C++程序中每一項(xiàng)操作基本都是由一個函數(shù)來實(shí)現(xiàn)的,C++程序中只能有一個主函數(shù)(main)

9.1 函數(shù)聲明與定義

  • 函數(shù)類型-函數(shù)的返回值類型;函數(shù)名-必須符合C++標(biāo)識符命名規(guī)則,后面必須跟一對括號;函數(shù)體-實(shí)現(xiàn)函數(shù)功能的主題部分;參數(shù)列表-函數(shù)名后面的括號內(nèi),用于向函數(shù)傳遞數(shù)值或帶回?cái)?shù)值。
  • 函數(shù)聲明中,參數(shù)名可以省略,參數(shù)類型和函數(shù)的類型不能省略。
  • 函數(shù)聲明可以放在主調(diào)函數(shù)內(nèi)部,放在調(diào)用語句之前;也可以放在主調(diào)函數(shù)外,如果位于所有定義函數(shù)之前,后面函數(shù)定義順序任意,各個主調(diào)函數(shù)調(diào)用也不必再做聲明
  • 當(dāng)函數(shù)定義在前,函數(shù)調(diào)用災(zāi)后,可以不用函數(shù)聲明。

后兩條總結(jié)一下就是:調(diào)用函數(shù)前,程序得知道有這個函數(shù),聲明就是提前讓程序知道有這么的玩意

函數(shù)聲明:

函數(shù)類型 函數(shù)名(參數(shù)列表);
eg:
int max(int a,int b);//聲明函數(shù)時,a,b可以省略
int max(int,int);
void show();

函數(shù)定義:

函數(shù)類型 函數(shù)名(參數(shù)列表)
{
	函數(shù)體;
}
eg:
int max(int a,int b)
{
	int z;
	z = a>b?a:b;
	return z;
}

9.2 函數(shù)的參數(shù)與返回值

  • 形參:函數(shù)定義后面括號里的參數(shù),函數(shù)調(diào)用前不占內(nèi)存。
  • 實(shí)參:函數(shù)調(diào)用括號里的參數(shù),可以是常量,變量或表達(dá)式等。

形參和實(shí)參必須個數(shù)相同、類型一致,順序一致
函數(shù)傳遞方式:傳值,指針,引用

關(guān)于指針和引用后面有詳細(xì)介紹。

//傳值-修改函數(shù)內(nèi)的形式參數(shù)對實(shí)際參數(shù)沒有影響
int add(int value)
{
	value++;
	return value;
}
int main()
{
	int v = 10;
	cout << "add() = " << add(v) << endl;//add() = 11
		cout << "v = " << v << endl;//v = 10
	return 0;
}
//指針-修改形式參數(shù)會影響實(shí)際參數(shù)
int add(int* pValue)
{
	(*pValue)++;
	return *pValue;
}
int main()
{
	int v = 10;
	cout << "add() = " << add(&v) << endl;//add() = 11
	cout << "v = " << v << endl;//v = 11
	return 0;
}
//引用-修改形式參數(shù)會影響實(shí)際參數(shù)
int add(int &value)
{
	value++;
	return value;
}
int main()
{
	int v = 10;
	cout << "add() = " << add(v) << endl;//add() = 11
	cout << "v = " << v << endl;//v = 11
	return 0;
}

有默認(rèn)值參數(shù)的函數(shù)

int sum(int a, int b=2)
{
  return (a + b);
}
int main ()
{
   cout << "Total value is :" << sum(100, 200);<< endl;//Total value is :300
   cout << "Total value is :" << sum(100);<< endl;//Total value is :102
   return 0;
}

函數(shù)的返回值

  • 返回值通過return給出,return后面跟表達(dá)式,且只能放回一個值;如果沒有表達(dá)式,可以不寫return;return后面的括號可有可無。
  • return語句中的表達(dá)式類型應(yīng)與函數(shù)類型一致,否則自動轉(zhuǎn)換類型(函數(shù)類型決定返回值類型)

9.3 函數(shù)調(diào)用

函數(shù)可以單獨(dú)作為一個語句使用。有返回值的函數(shù),可將函數(shù)調(diào)用作為語句的一部分,利用返回值參與運(yùn)算。
函數(shù)調(diào)用形式:參數(shù)傳遞–>函數(shù)體執(zhí)行–>返回主調(diào)函數(shù)

函數(shù)名(實(shí)參列表);
show();

函數(shù)的嵌套調(diào)用:

int a()
{
	return 666;
}
int b(int sum)
{
	return sum+a()
}
int main()
{
	cout<<b(222)<<endl;//888
	return 0;
}

函數(shù)的遞歸調(diào)用:直接遞歸調(diào)用和間接遞歸調(diào)用

  • 一個函數(shù)直接或間接遞歸調(diào)用該函數(shù)本身,稱為函數(shù)的遞歸調(diào)用
  • 遞歸和回歸:原問題=>子問題 子問題的解=>原問題的解
//直接遞歸調(diào)用:求1+...n的值
int total(int sum)
{
	if (sum == 1)
	{
		return 1;
	}
	return sum + total(sum - 1);
}
int main()
{
	cout << "total = " << total(10) << endl;//total = 55
	system("pause");
	return 0;
}
//間接遞歸調(diào)用
int f2();
int f1()
{
...
  f2()
}
int f2()
{
	f1();
}

9.4 函數(shù)重載

    同一個函數(shù)名對應(yīng)不同的函數(shù)實(shí)現(xiàn),每一類實(shí)現(xiàn)對應(yīng)著一個函數(shù)體,名字相同,功能相同,只是參數(shù)的類型或參數(shù)的個數(shù)不同。
多個同名函數(shù)只是函數(shù)類型(函數(shù)返回值類型)不同時,它們不是重載函數(shù)

int add(int a,int b)
{
	return a+b;
}
double add(double a,double b)
{
	return a+b;
}
int add(int a,int b,int c)
{
	return a+b+c;
}

9.5 內(nèi)聯(lián)(inline)函數(shù)

    c++在編譯時可以講調(diào)用的函數(shù)代碼嵌入到主調(diào)函數(shù)中,這種嵌入到主調(diào)函數(shù)中的函數(shù)稱為內(nèi)聯(lián)函數(shù),又稱為內(nèi)嵌函數(shù)或內(nèi)置函數(shù)。

  • 定義內(nèi)聯(lián)函數(shù)時,在函數(shù)定義和函數(shù)原型聲明時都使用inline,也可以只在其中一處使用,其效果一樣。
  • 內(nèi)聯(lián)函數(shù)在編譯時用內(nèi)聯(lián)函數(shù)函數(shù)的函數(shù)體替換,所以不發(fā)生函數(shù)調(diào)用,不需要保護(hù)現(xiàn)場,恢復(fù)現(xiàn)場,節(jié)省了開銷。
  • 內(nèi)聯(lián)函數(shù)增加了目標(biāo)程序的代碼量。因此,一般只將函數(shù)規(guī)模很小且使用頻繁的函數(shù)聲明為內(nèi)聯(lián)函數(shù)。
  • 當(dāng)內(nèi)聯(lián)函數(shù)中實(shí)現(xiàn)過于復(fù)雜時,編譯器會將它作為一個普通函數(shù)處理,所以內(nèi)聯(lián)函數(shù)內(nèi)不能包含循環(huán)語句和switch語句。

內(nèi)聯(lián)函數(shù)格式如下:

inline 函數(shù)類型 函數(shù)名(形參列表)
{
	函數(shù)體;
}
inline int add(int a, int b)
{
	return a + b;
}

9.6 洞悉內(nèi)聯(lián)函數(shù)底層原理

1.使用Visual Studio 2015創(chuàng)建一個C++Win32控制臺程序,點(diǎn)擊項(xiàng)目->項(xiàng)目屬性設(shè)置內(nèi)聯(lián)函數(shù)優(yōu)化

在這里插入圖片描述

2.編寫內(nèi)聯(lián)函數(shù)代碼,設(shè)置斷點(diǎn),debug啟動

#include <iostream>
#include <string>
using namespace std;
inline int add(int a, int b)
{
	return a + b;//斷點(diǎn)1
}
int main()
{
	int result = add(12, 34);
	cout << result << endl;//斷點(diǎn)2
	return 0;
}

3.調(diào)試->窗口->反匯編,然后就能看到編譯后的匯編程序

...
		int result = add(12, 34);
00B620DE  mov         eax,0Ch  
00B620E3  add         eax,22h  //對eax中和22h中值進(jìn)行相加,賦值給eax
00B620E6  mov         dword ptr [result],eax  
		cout << result << endl;
00B620E9  mov         esi,esp  
00B620EB  push        offset std::endl<char,std::char_traits<char> > (0B610A5h)  
00B620F0  mov         edi,esp  
00B620F2  mov         eax,dword ptr [result]  
00B620F5  push        eax  
00B620F6  mov         ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0B6D098h)]  
00B620FC  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B6D0A8h)]  
00B62102  cmp         edi,esp  
00B62104  call        __RTC_CheckEsp (0B611C7h)  
00B62109  mov         ecx,eax  
00B6210B  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B6D0ACh)]  
00B62111  cmp         esi,esp  
00B62113  call        __RTC_CheckEsp (0B611C7h)   
		return 0;

4.從匯編代碼中可以代碼編譯后內(nèi)聯(lián)函數(shù)直接嵌入到主函數(shù)中,并且斷點(diǎn)1不會執(zhí)行到,下面是沒使用內(nèi)聯(lián)函數(shù)(去掉inline關(guān)鍵字)的匯編代碼:

int result = add(12, 34);
00291A4E  push        22h  
00291A50  push        0Ch  
00291A52  call        add (02914D8h)  //調(diào)用add函數(shù)
00291A57  add         esp,8//移動堆棧指針esp,繼續(xù)執(zhí)行主函數(shù)
00291A5A  mov         dword ptr [result],eax  
		cout << result << endl;
00291A5D  mov         esi,esp  
00291A5F  push        offset std::endl<char,std::char_traits<char> > (02910A5h)  
00291A64  mov         edi,esp  
00291A66  mov         eax,dword ptr [result]  
00291A69  push        eax  
00291A6A  mov         ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (029D098h)]  
		cout << result << endl;
00291A70  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (029D0A8h)]  
00291A76  cmp         edi,esp  
00291A78  call        __RTC_CheckEsp (02911C7h)  
00291A7D  mov         ecx,eax  
00291A7F  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (029D0ACh)]  
00291A85  cmp         esi,esp  
00291A87  call        __RTC_CheckEsp (02911C7h)  
		system("pause");
00291A8C  mov         esi,esp  
00291A8E  push        offset string "pause" (0299B30h)  
00291A93  call        dword ptr [__imp__system (029D1DCh)]  
00291A99  add         esp,4  
00291A9C  cmp         esi,esp  
00291A9E  call        __RTC_CheckEsp (02911C7h)  
		return 0;

從以上代碼代碼可以看出,在主函數(shù)中調(diào)用(call)了add函數(shù)。

5.在內(nèi)聯(lián)函數(shù)中添加幾個循環(huán)后,編譯器就把內(nèi)聯(lián)函數(shù)當(dāng)做普通函數(shù)看待了,代碼如下:

inline int add(int a, int b)
{
	int sum = 0;
	for (int i = 0; i < 100; i++)
		a++;
	for (int i = 0; i < 100; i++)
	{
		for (int i = 0; i < 100; i++)
		{
			sum++;
		}
	}
	return a + b;
}
int main()
{
	int result = add(12, 34);
	cout << result << endl;
	return 0;
}
int result = add(12, 34);
00181A4E  push        22h  
00181A50  push        0Ch  
00181A52  call        add (01814ECh)  ///
00181A57  add         esp,8  
00181A5A  mov         dword ptr [result],eax  
	cout << result << endl;
00181A5D  mov         esi,esp  
00181A5F  push        offset std::endl<char,std::char_traits<char> > (01810A5h)  
00181A64  mov         edi,esp  
00181A66  mov         eax,dword ptr [result]  
00181A69  push        eax  
00181A6A  mov         ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (018D098h)]  
	cout << result << endl;
00181A70  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (018D0A8h)]  
00181A76  cmp         edi,esp  
00181A78  call        __RTC_CheckEsp (01811C7h)  
00181A7D  mov         ecx,eax  
00181A7F  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (018D0ACh)]  
00181A85  cmp         esi,esp  
00181A87  call        __RTC_CheckEsp (01811C7h)  
	return 0;
00181AA3  xor         eax,eax  

10、字符串(string)

10.1 C風(fēng)格的字符串(字符數(shù)組)

C風(fēng)格的字符串實(shí)際上是使用 null 字符 ‘\0’ 終止的一維字符數(shù)組。

輸入字符串長度一定小于已定義的字符數(shù)組長度,最后一位是/0終止符號;不然輸出時無法知道在哪里結(jié)束。
字符數(shù)組的定義和初始化

char a[5]
//字符個數(shù)不夠,補(bǔ)0; 字符個數(shù)超過報(bào)錯
char str[7] = {'h','e','i','r','e','n'};
char str[] = {'h','e','i','r','e','n'};
cin>>str;//輸入 輸入字符串長度一定小于已定義的字符數(shù)組長度
cout<<str;//輸出

字符串的處理函數(shù)

strcat(char s1[],const char s2[]);//將s2接到s1上
strcpy(char s1[],const char s2[]);//將s2復(fù)制到s1上
strcmp(const char s1[],const char s2[]);//比較s1,s2 s1>s2返回1 相等返回1,否則返回-1
strlen(char s[]);//計(jì)算字符串s的長度 字符串s的實(shí)際長度,不包括\0在內(nèi)

10.2 C++中的字符串(string)

字符串的定義和初始化

//定義
string 變量;
string str1;
//賦值
string str2 = "ShangHai";
string str3 = str2;
str3[3] = '2';//對某個字符賦值
//字符串?dāng)?shù)組
string 數(shù)組名[常量表達(dá)式]
string arr[3];

字符串的處理函數(shù)

#include <iostream>
#include <algorithm>
#include <string>
string str;//生成空字符串
string s(str);//生成字符串為str的復(fù)制品
string s(str, strbegin,strlen);//將字符串str中從下標(biāo)strbegin開始、長度為strlen的部分作為字符串初值
string s(cstr, char_len);//以C_string類型cstr的前char_len個字符串作為字符串s的初值
string s(num ,c);//生成num個c字符的字符串
string s(str, stridx);//將字符串str中從下標(biāo)stridx開始到字符串結(jié)束的位置作為字符串初值
size()和length();//返回string對象的字符個數(shù)
max_size();//返回string對象最多包含的字符數(shù),超出會拋出length_error異常
capacity();//重新分配內(nèi)存之前,string對象能包含的最大字符數(shù)
>,>=,<,<=,==,!=//支持string與C-string的比較(如 str<”hello”)。  使用>,>=,<,<=這些操作符的時候是根據(jù)“當(dāng)前字符特性”將字符按字典順序進(jìn)行逐一得 比較,string (“aaaa”) <string(aaaaa)。    
compare();//支持多參數(shù)處理,支持用索引值和長度定位子串來進(jìn)行比較。返回一個整數(shù)來表示比較結(jié)果,返回值意義如下:0:相等 1:大于 -1:
push_back() 
insert( size_type index, size_type count, CharT ch );//在index位置插入count個字符ch
insert( size_type index, const CharT* s );//index位置插入一個常量字符串
insert( size_type index, const CharT* s, size_type n);//index位置插入常量字符串
insert( size_type index, const basic_string& str );//index位置插入常量string中的n個字符
insert( size_type index, const basic_string& str, size_type index_str, size_type n);//index位置插入常量str的從index_str開始的n個字符
insert( size_type index, const basic_string& str,size_type index_str, size_type count = npos);//index位置插入常量str從index_str開始的count個字符,count可以表示的最大值為npos.這個函數(shù)不構(gòu)成重載 npos表示一個常數(shù),表示size_t的最大值,string的find函數(shù)如果未找到指定字符,返回的就是一個npos
iterator insert( iterator pos, CharT ch );
iterator insert( const_iterator pos, CharT ch );
void insert( iterator pos, size_type n, CharT ch );//迭代器指向的pos位置插入n個字符ch
iterator insert( const_iterator pos, size_type count, CharT ch );//迭代器指向的pos位置插入count個字符ch
void insert( iterator pos, InputIt first, InputIt last );
iterator insert( const_iterator pos, InputIt first, InputIt last );
append() 和 + 操作符
//訪問string每個字符串
string s1("yuanrui"); // 調(diào)用一次構(gòu)造函數(shù)
// 方法一: 下標(biāo)法
for( int i = 0; i < s1.size() ; i++ )
     cout<<s1[i];
// 方法二:正向迭代器
for( string::iterator iter = s1.begin();; iter < s1.end() ; iter++)
     cout<<*iter;
 // 方法三:反向迭代器
for(string::reverse_iterator riter = s1.rbegin(); ; riter < s1.rend() ; riter++)
     cout<<*riter;
 iterator erase(iterator p);//刪除字符串中p所指的字符
iterator erase(iterator first, iterator last);//刪除字符串中迭代器區(qū)間[first,last)上所有字符
string& erase(size_t pos = 0, size_t len = npos);//刪除字符串中從索引位置pos開始的len個字符
void clear();//刪除字符串中所有字符
string& replace(size_t pos, size_t n, const char *s);//將當(dāng)前字符串從pos索引開始的n個字符,替換成字符串s
string& replace(size_t pos, size_t n, size_t n1, char c); //將當(dāng)前字符串從pos索引開始的n個字符,替換成n1個字符c
string& replace(iterator i1, iterator i2, const char* s);//將當(dāng)前字符串[i1,i2)區(qū)間中的字符串替換為字符串s
//tolower()和toupper()函數(shù) 或者 STL中的transform算法
string s = "ABCDEFG";
for( int i = 0; i < s.size(); i++ )
     s[i] = tolower(s[i]);
transform(s.begin(),s.end(),s.begin(),::tolower);
size_t find (constchar* s, size_t pos = 0) const;//在當(dāng)前字符串的pos索引位置開始,查找子串s,返回找到的位置索引,-1表示查找不到子串
size_t find (charc, size_t pos = 0) const;//在當(dāng)前字符串的pos索引位置開始,查找字符c,返回找到的位置索引,-1表示查找不到字符
size_t rfind (constchar* s, size_t pos = npos) const;//在當(dāng)前字符串的pos索引位置開始,反向查找子串s,返回找到的位置索引,-1表示查找不到子串
size_t rfind (charc, size_t pos = npos) const;//在當(dāng)前字符串的pos索引位置開始,反向查找字符c,返回找到的位置索引,-1表示查找不到字符
size_tfind_first_of (const char* s, size_t pos = 0) const;//在當(dāng)前字符串的pos索引位置開始,查找子串s的字符,返回找到的位置索引,-1表示查找不到字符
size_tfind_first_not_of (const char* s, size_t pos = 0) const;//在當(dāng)前字符串的pos索引位置開始,查找第一個不位于子串s的字符,返回找到的位置索引,-1表示查找不到字符
size_t find_last_of(const char* s, size_t pos = npos) const;//在當(dāng)前字符串的pos索引位置開始,查找最后一個位于子串s的字符,返回找到的位置索引,-1表示查找不到字符
size_tfind_last_not_of (const char* s, size_t pos = npos) const;//在當(dāng)前字符串的pos索引位置開始,查找最后一個不位于子串s的字符,返回找到的位置索引,-1表示查找不到子串
sort(s.begin(),s.end());
substr(pos,n);//返回字符串從下標(biāo)pos開始n個字符
strtok()
char str[] = "I,am,a,student; hello world!";
const char *split = ",; !";
char *p2 = strtok(str,split);
while( p2 != NULL )
{
    cout<<p2<<endl;
    p2 = strtok(NULL,split);
}

11、指針和引用

11.1 指針

指針是一個變量,其值為另一個變量的地址。即內(nèi)存位置的直接地址。
聲明的一般形式:

數(shù)據(jù)類型是指針變量所指向的變量的數(shù)據(jù)類型,*表示其后的變量為指針變量

數(shù)據(jù)類型 *指針變量名;
int    *ip;    //整型的指針 
double *dp;    //double 型的指針 
float  *fp;    //浮點(diǎn)型的指針 
char   *ch;    //字符型的指針 

指針變量的初始化:

  • &是取地址運(yùn)算符,&變量名表示變量的地址。
  • 變量的數(shù)據(jù)類型必須于指針變量的數(shù)據(jù)類型一致。
  • 為了安全起見,有時會把指針初始化為空指針(NULL或0)
數(shù)據(jù)類型 *指針變量名 = &變量名;
*指針變量名 = &變量名;
int a;
int *p = &a;
int *p2;
p2 = &a;

指針變量的引用:

  • & 取地址符 * 指針運(yùn)算符(間接運(yùn)算符),其后是指針變量,表示該指針變量所指向的變量。
  • & *的優(yōu)先級是相同的,結(jié)合方式都是自左向右。比如 &*p等價于&(*p)。
int x = 3;
int y;
int *p;
p = &x;
y = *p;//y = a

指針運(yùn)算(地址運(yùn)算)

  • 算術(shù)運(yùn)算(移動指針運(yùn)算):加減,自增自減。
  • p+n運(yùn)算得到的地址是p+n*sizeof(數(shù)據(jù)類型)。
  • 兩個相同數(shù)據(jù)類型的指針可以進(jìn)行加減運(yùn)算,一般用于數(shù)組的操作中。
  • 關(guān)系運(yùn)算:指針指向同一串連續(xù)存儲單元才有意義,比如數(shù)組。與0比較,判斷是不是空指針。
  • 賦值運(yùn)算:變量地址賦值給指針變量,數(shù)組元素地址賦值給指針變量,指針變量賦值給其他指針變量。
int arr[10],len;
int *p1 = &arr[2],*p2 = &arr[5];
 len = p2-p1;//arr[2] 和arr[5]之間的元素個數(shù) 3

new和delete運(yùn)算符

  • new-為變量分配內(nèi)存空間;
  • 可以通過判斷new返回的指針的值,判斷空間是否分配成功。
  • delete-釋放空間
指針變量 = new 數(shù)據(jù)類型(初值);
delete 指針變量;
delete[] 指針變量;//釋放為多個變量分配的地址
int *ip;
ip= new int(1);
delete ip;
int *ip;
 ip= new int[10];
 for (int i = 0; i < 10;i++)
 {
	 ip[i] = i;
 }
 delete[] ip;
int a[3][4] = {0};

指針與數(shù)組

  • 數(shù)組名是數(shù)組的首地址,eg:arr為arr[0]的地址。
  • 訪問數(shù)組元素:arr[i],(arr+i),(p+i),p[i]
  • 二維數(shù)組:arr+i == &arr[i],arr[i] == &arr[i][0] ,*(arr[i]+j) == arr[i][j]
  • 指針訪問二維數(shù)組:指向二維數(shù)組元素,指向一維數(shù)組
  • 數(shù)組指針:數(shù)據(jù)類型 (*指針變量名) [m]
int arr[10];
int *p1 = arr;// *p1 = &arr[0];
int a[3][5] = { 0 };
int(*ap)[5];
ap = a;
ap+1;//表示下一個一維數(shù)組

指針與字符串

  • 字符串?dāng)?shù)組名:char ch[] = "heiren";char *p = ch;
  • 字符串:char *p = "heiren";
  • 指針賦值運(yùn)算:char * p;p = "Heiren";指針與函數(shù),指針可以作為函數(shù)的參數(shù),也可以作為函數(shù)的返回值。

11.2 引用

引用可以看做是數(shù)據(jù)的一個別名,通過這個別名和原來的名字都能夠找到這份數(shù)據(jù),類似于window中的快捷方式。

  • 引用不占內(nèi)存空間,必須在定義的同時初始化,且不能再引用其他數(shù)據(jù)。
  • 引用在定義時需要添加&,在使用時不能添加&,使用時添加&表示取地址
    引用型變量聲明:數(shù)據(jù)類型 &引用名 = 變量名;
int a;
int &b = a;//a和b表示相同的變量,具有相同的地址。

引用可以作為函數(shù)參數(shù),也可以作為函數(shù)返回值。

void swap(int &r1, int &r2) {
    int temp = r1;
    r1 = r2;
    r2 = temp;
}
int &add1(int &r) {
	r += 1;
	return r;
}
int main()
{
	int a = 12;
	int b = add1(a);
	cout << a << "   "<<b << endl;//13  13
	return 0;
}

將引用作為函數(shù)返回值時不能返回局部數(shù)據(jù)的引用,因?yàn)楫?dāng)函數(shù)調(diào)用完成后局部數(shù)據(jù)就會被銷毀。
函數(shù)在棧上運(yùn)行,函數(shù)掉用完,后面的函數(shù)調(diào)用會覆蓋之前函數(shù)的局部數(shù)據(jù)。

int &add1(int &r) {
	r += 1;
	int res = r;
	return res;
}
void test()
{
	int xx = 123;
	int yy = 66;
}
int main()
{
	int a = 12;
	int &b = add1(a);
	int &c = add1(a);
	test();//函數(shù)調(diào)用,覆蓋之前函數(shù)的局部數(shù)據(jù)
	cout << a << "   "<<b <<" "<< c<<endl;//14   -858993460 -858993460
	return 0;
}

12、自定義數(shù)據(jù)類型

12.1 結(jié)構(gòu)體

結(jié)構(gòu)體可以包含不同數(shù)據(jù)類型的結(jié)構(gòu)。
定義結(jié)構(gòu)體的一般形式

struct 結(jié)構(gòu)體類型名
{
	成員類型1 成員名1;
	成員類型2 成員名2;
	... ...
	成員類型n 成員名n;
};

結(jié)構(gòu)體變量名的定義和初始化:

//定義結(jié)構(gòu)體同時聲明結(jié)構(gòu)體變量名
struct 結(jié)構(gòu)體類型名
{
	成員類型1 成員名1;
	成員類型2 成員名2;
	... ...
	成員類型n 成員名n;
}變量名1,變量名2,...變量名n;
//先定義結(jié)構(gòu)體
[struct] 結(jié)構(gòu)體類型名 變量名;
//直接定義
struct 
{
	成員類型1 成員名1;
	成員類型2 成員名2;
	... ...
	成員類型n 成員名n;
}變量名1,變量名2,...變量名n;
struct  person
{
	int year;
	int age;
	string name;
}p1 = {2019,24,"heiren"}, p1 = { 2020,24,"heiren" };
struct  person
{
	int year;
	int age;
	string name;
};
struct person p1 = { 2019,24,"heiren" }, p1 = { 2020,24,"heiren" };
struct 
{
	int year;
	int age;
	string name;
}p1 = {2019,24,"heiren"}, p1 = { 2020,24,"heiren" };

結(jié)構(gòu)體變量的使用:

  • 具有相同類型的結(jié)構(gòu)體變量可以進(jìn)行賦值運(yùn)算,但是不能輸入輸出
  • 對結(jié)構(gòu)體變量的成員引用:結(jié)構(gòu)體變量名.成員名
  • 指向結(jié)構(gòu)體的指針變量引用格式:指針變量名->成員名;結(jié)構(gòu)體數(shù)組的定義,初始化和使用與結(jié)構(gòu)體變量、基本類型數(shù)組相似
struct person
{
	int year;
	int age;
	string name;
}p[2] ={ {2019,24,"heiren"}, { 2020,24,"heiren" }};//可以不指定數(shù)組元素個數(shù)
p[1].age;

結(jié)構(gòu)體作為函數(shù)傳遞有三種:值傳遞,引用傳遞,指針傳遞

12.2 結(jié)構(gòu)體大小和字節(jié)對齊

    現(xiàn)代計(jì)算機(jī)中內(nèi)存空間都是按照byte劃分的,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但實(shí)際情況是在訪問特定類型變量的時候經(jīng)常在特 定的內(nèi)存地址訪問,這就需要各種類型數(shù)據(jù)按照一定的規(guī)則在空間上排列,而不是順序的一個接一個的排放,這就是對齊.
    為什么需要字節(jié)對齊?各個硬件平臺對存儲空間的處理上有很大的不同。一些平臺對某些特定類型的數(shù)據(jù)只能從某些特定地址開始存取。比如有些平臺每次讀都是從偶地址開始,如果一個int型(假設(shè)為32位系統(tǒng))如果存放在偶地址開始的地方,那么一個讀周期就可以讀出這32bit,而如果存放在奇地址開始的地方,就需要2個讀周期,并對兩次讀出的結(jié)果的高低字節(jié)進(jìn)行拼湊才能得到該32bit數(shù)據(jù)。

三個個概念:

  • 自身對齊值:數(shù)據(jù)類型本身的對齊值,結(jié)構(gòu)體或類的的自身對齊值是其成員中最大的那個值,例如char類型的自身對齊值是1,short類型是2;
  • 指定對齊值:編譯器或程序員指定的對齊值,32位單片機(jī)的指定對齊值默認(rèn)是4;
  • 有效對齊值:自身對齊值和指定對齊值中較小的那個。
    字節(jié)對齊的三個準(zhǔn)則
  • 結(jié)構(gòu)體變量的首地址能夠被其有效對齊值的大小所整除
  • 結(jié)構(gòu)體的總大小為結(jié)構(gòu)體有效對齊值的整數(shù)倍。
  • 結(jié)構(gòu)體每個成員相對于結(jié)構(gòu)體首地址的偏移量都是有效對齊值的整數(shù)倍。

可以通過#pragma pack(n)來設(shè)定變量以n字節(jié)對齊方式

舉個例子

//指定對齊值=8
struct st 
{
	// 空結(jié)構(gòu)體大小1
	char c1;//1
	char c2;//2
	int i1;//8  int  起始地址按照字節(jié)對齊的原理應(yīng)該是它長度4的整數(shù)倍
	char c3;//12
	short s4;//12 short 起始地址按照字節(jié)對齊的原理應(yīng)該是它長度2的整數(shù)倍 12 + 2 = 12
	double d;//24 double 起始地址按照字節(jié)對齊的原理應(yīng)該是它長度8的整數(shù)倍 12->16 + 8 = 24
	char c4;//32   24 + 4 = 28 結(jié)構(gòu)體的總大小為8的整數(shù)倍 28->32
	int i2;//32  28+4 = 32
	int i3;//40   
	short s5;//40
};
cout << sizeof(st) << endl;//40
//指定對齊值=4
#pragma  pack(4)
struct st
{
	//1 空結(jié)構(gòu)體大小1
	char c1;//1
	char c2;//2
	int i1;//8
	char c3;//12
	short s4;//12
	double d;//20
	char c4;//24
	int i2;//28
	int i3;//32
	short s5;//36
}s;
cout << sizeof(st) << endl;//36

字節(jié)對齊

12.3 公用體(union)

幾個不同的變量共享同一個地址開始的內(nèi)存空間。

  • 成員類型可以是基本數(shù)據(jù)類型,也可以是構(gòu)造數(shù)據(jù)類型。
  • 公用體變量初始化時,只能對第一個成員賦值。
  • 公用體變量所占的內(nèi)存長度等于最長的成員長度。
  • 公用體變量在一個時刻只能一個成員發(fā)揮作用,賦值時,成員之間會互相覆蓋,最后一次被賦值的成員起作用。

定義

union 共同體類型名
{
    成員類型1 成員名1;
	成員類型2 成員名2;
	... ...
	成員類型n 成員名n;
};

初始化

union data
{
	int i;
	float f;
	char c;
}x = {123};
union data
{
	float f;
	int i;
	char c;
};
data x = {12.3};
union 
{
	char c;
	int i;
	float f;
}x = {'y‘};

引用

共同體變量名.成員名;
union data
{
	int i;
	float f;
	char c;
}x = {12};
int main()
{
	cout << x.i << " " << x.f << " " << x.c << endl;//12 1.68156e-44 
	x.c = 'c';
	cout << x.i <<" "<< x.f << " " << x.c << endl;//99 1.38729e-43 c
	return 0;
}

12.4 枚舉(enum)和typedef聲明

枚舉已經(jīng)在前面的章節(jié)介紹過,這里就不在贅述了。
typedef-為已存在的數(shù)據(jù)類型定義一個新的類型名稱,不能定義變量。
typedef聲明格式:typedef 類型名稱 類型標(biāo)識符;

typedef char *CP;
typedef int INTEGER;

13、面向?qū)ο?/h2>

13.1 類

類也是一種數(shù)據(jù)類型。

類的聲明:

class 類名
{
	public:
		公有數(shù)據(jù)成員;
		公有成員函數(shù);
	private:
		私有數(shù)據(jù)成員;
		私有成員函數(shù);
	protected:
		保護(hù)數(shù)據(jù)成員;
		保護(hù)成員函數(shù);
};

成員函數(shù)的定義:類內(nèi),類外,類外內(nèi)聯(lián)函數(shù)

//類外
返回類型 類名:成員函數(shù)名(參數(shù)列表)
{
	函數(shù)體;
}
//內(nèi)聯(lián)函數(shù):類外
inline 返回類型 類名:成員函數(shù)名(參數(shù)列表)
{
	函數(shù)體;
}

內(nèi)聯(lián)函數(shù)的代碼會直接嵌入到主調(diào)函數(shù)中,可以節(jié)省調(diào)用時間,如果成員函數(shù)在類內(nèi)定義,自動為內(nèi)聯(lián)函數(shù)。

13.2 類成員的訪問權(quán)限以及類的封裝

  • 和Java、C#不同的是,C++中public、private、protected只能修飾類的成員,不能修飾類,C++中的類沒有共有私有之分
  • 類內(nèi)部沒有訪問權(quán)限的限制,都可以互相訪問。
  • 在C++中用class定義的類中,其成員的默認(rèn)存取權(quán)限是private。
類外派生類類內(nèi)
publicYYY
protectedNYY
privateNNY

13.3 對象

//1.聲明類同時定義對象
class 類名
{
	類體;
}對象名列表;
//2.先聲明類,再定義對象
類名 對象名(參數(shù)列表);//參數(shù)列表為空時,()可以不寫
//3. 不出現(xiàn)類名,直接定義對象
class 
{
	類體;
}對象名列表;
//4.在堆上創(chuàng)建對象
Person p(123, "yar");//在棧上創(chuàng)建對象
Person *pp = new Person(234,"yar");//在堆上創(chuàng)建對象

注:不可以在定義類的同時對其數(shù)據(jù)成員進(jìn)行初始化,因?yàn)轭惒皇且粋€實(shí)體,不合法但是能編譯運(yùn)行

對象成員的引用:對象名.數(shù)據(jù)成員名 或者 對象名.成員函數(shù)名(參數(shù)列表)

13.4 構(gòu)造函數(shù)

是一種特殊的成員函數(shù),主要功能是為對象分配存儲空間,以及為類成員變量賦初值

  • 構(gòu)造函數(shù)名必須與類名相同
  • 沒有任何返回值和返回類型
  • 創(chuàng)建對象自動調(diào)用,不需要用戶來調(diào)用,且只掉用一次
  • 類沒有定義任何構(gòu)造函數(shù),編譯系統(tǒng)會自動為這個類生成一個默認(rèn)的無參構(gòu)造函數(shù)

構(gòu)造函數(shù)定義

//1.類中定義 2.類中聲明,類外定義
[類名::]構(gòu)造函數(shù)名(參數(shù)列表)
{
	函數(shù)體
}

創(chuàng)建對象

類名 對象名(參數(shù)列表);//參數(shù)列表為空時,()可以不寫

帶默認(rèn)參數(shù)的構(gòu)造函數(shù)

class Person
{
public:
	Person(int = 0,string = "張三");
	void show();
private:
	int age;
	string name;
};
Person::Person(int a, string s)
{
	cout<<a<<" "<<s<<endl;
	age = a;
	name = s;
}
void Person::show()
{
	cout << "age="<<age << endl;
	cout << "name=" <<name << endl;
}
int main()
{
     Person p; //0 張三
	Person p2(12);//12 張三
	Person p3(123, "yar");//123 yar
	return 0;
}

帶參數(shù)初始化表的構(gòu)造函數(shù)

類名::構(gòu)造函數(shù)名(參數(shù)列表):參數(shù)初始化表
{
	函數(shù)體;
}
參數(shù)初始化列表的一般形式:
參數(shù)名1(初值1),參數(shù)名2(初值2),...,參數(shù)名n(初值n)
class Person
{
public:
	Person(int = 0,string = "張三");
	void show();
private:
	int age;
	string name;
};
Person::Person(int a, string s):age(a),name(s)
{
	cout << a << " " << s << endl;
}

構(gòu)造函數(shù)重載:構(gòu)造函數(shù)名字相同,參數(shù)個數(shù)和參數(shù)類型不一樣。

class Person
{
public:
	Person();
	Person(int = 0,string = "張三");
	Person(double,string);
	void show();
private:
	int age;
	double height;
	string name;
};
...

拷貝構(gòu)造函數(shù)

類名::類名(類名&對象名)
{
	函數(shù)體;
}
class Person
{
public:
	Person(Person &p);//聲明拷貝構(gòu)造函數(shù)
	Person(int = 0,string = "張三");
	void show();
private:
	int age;
	string name;
};
Person::Person(Person &p)//定義拷貝構(gòu)造函數(shù)
{
	cout << "拷貝構(gòu)造函數(shù)" << endl;
	age = 0;
	name = "ABC";
}
Person::Person(int a, string s):age(a),name(s)
{
	cout << a << " " << s << endl;
}
int main()
{
	Person p(123, "yar");
	Person p2(p);
	p2.show();
	return 0;
}
//輸出
123 yar
拷貝構(gòu)造函數(shù)
age=0
name=ABC

13.5 析構(gòu)函數(shù)

是一種特殊的成員函數(shù),當(dāng)對象的生命周期結(jié)束時,用來釋放分配給對象的內(nèi)存空間愛你,并做一些清理的工作。

  • 析構(gòu)函數(shù)名與類名必須相同。
  • 析構(gòu)函數(shù)名前面必須加一個波浪號~。
  • 沒有參數(shù),沒有返回值,不能重載。
  • 一個類中只能有一個析構(gòu)函數(shù)。
  • 沒有定義析構(gòu)函數(shù),編譯系統(tǒng)會自動為和這個類生成一個默認(rèn)的析構(gòu)函數(shù)。

析構(gòu)函數(shù)的定義:

//1.類中定義 2.類中聲明,類外定義
[類名::]~析構(gòu)函數(shù)名()
{
	函數(shù)體;
}

13.6 對象指針

對象指針的聲明和使用

類名 *對象指針名;
對象指針 = &對象名;
//訪問對象成員
對象指針->數(shù)據(jù)成員名
對象指針->成員函數(shù)名(參數(shù)列表)
Person p(123, "yar");
Person* pp = &p;
Person* pp2 = new Person(234,"yar")
pp->show();

指向?qū)ο蟪蓡T的指針

數(shù)據(jù)成員類型 *指針變量名 = &對象名.數(shù)據(jù)成員名;
函數(shù)類型 (類名::*指針變量名)(參數(shù)列表);
指針變量名=&類名::成員函數(shù)名;
(對象名.*指針變量名)(參數(shù)列表);
Person p(123, "yar");
void(Person::*pfun)();
pfun = &Person::show;
(p.*pfun)();

this指針

每個成員函數(shù)都有一個特殊的指針this,它始終指向當(dāng)前被調(diào)用的成員函數(shù)操作的對象

class Person
{
public:
	Person(int = 0,string = "張三");
	void show();
private:
	int age;
	string name;
};
Person::Person(int a, string s):age(a),name(s)
{
	cout << a << " " << s << endl;
}
void Person::show()
{
	cout << "age="<<this->age << endl;
	cout << "name=" <<this->name << endl;
}

13.7 靜態(tài)成員

以關(guān)鍵字static開頭的成員為靜態(tài)成員,多個類共享。

  • static 成員變量屬于類,不屬于某個具體的對象
  • 靜態(tài)成員函數(shù)只能訪問類中靜態(tài)數(shù)據(jù)成員

靜態(tài)數(shù)據(jù)成員

//類內(nèi)聲明,類外定義
class xxx
{
	static 數(shù)據(jù)類型 靜態(tài)數(shù)據(jù)成員名;
}
數(shù)據(jù)類型 類名::靜態(tài)數(shù)據(jù)成員名=初值
//訪問
類名::靜態(tài)數(shù)據(jù)成員名;
對象名.靜態(tài)數(shù)據(jù)成員名;
對象指針名->靜態(tài)數(shù)據(jù)成員名;

靜態(tài)成員函數(shù)

//類內(nèi)聲明,類外定義
class xxx
{
	static 返回值類型 靜態(tài)成員函數(shù)名(參數(shù)列表);
}
返回值類型 類名::靜態(tài)成員函數(shù)名(參數(shù)列表)
{
	函數(shù)體;
}
//訪問
類名::靜態(tài)成員函數(shù)名(參數(shù)列表);
對象名.靜態(tài)成員函數(shù)名(參數(shù)列表);
對象指針名->靜態(tài)成員函數(shù)名(參數(shù)列表);

13.8 友元

借助友元(friend),可以使得其他類中得成員函數(shù)以及全局范圍內(nèi)得函數(shù)訪問當(dāng)前類得private成員。

友元函數(shù)

  • 友元函數(shù)不是類的成員函數(shù),所以沒有this指針,必須通過參數(shù)傳遞對象。
  • 友元函數(shù)中不能直接引用對象成員的名字,只能通過形參傳遞進(jìn)來的對象或?qū)ο笾羔榿硪迷搶ο蟮某蓡T。
//1.將非成員函數(shù)聲明為友元函數(shù)
class Person
{
public:
	Person(int = 0,string = "張三");
	friend void show(Person *pper);//將show聲明為友元函數(shù)
private:
	int age;
	string name;
};
Person::Person(int a, string s):age(a),name(s)
{
	cout << a << " " << s << endl;
}
void show(Person *pper)
{
	cout << "age="<< pper->age << endl;
	cout << "name=" << pper->name << endl;
}
int main()
{;
	Person *pp = new Person(234,"yar");
	show(pp);
	system("pause");
	return 0;
}
//2.將其他類的成員函數(shù)聲明為友元函數(shù)
//person中的成員函數(shù)可以訪問MobilePhone中的私有成員變量
class MobilePhone;//提前聲明
//聲明Person類
class Person
{
public:
	Person(int = 0,string = "張三");
	void show(MobilePhone *mp);
private:
	int age;
	string name;
};
//聲明MobilePhone類
class MobilePhone
{
public:
	MobilePhone();
	friend void Person::show(MobilePhone *mp);
private:
	int year;
	int memory;
	string name;
};
MobilePhone::MobilePhone()
{
	year = 1;
	memory = 4;
	name = "iphone 6s";
}
Person::Person(int a, string s):age(a),name(s)
{
	cout << a << " " << s << endl;
}
void Person::show(MobilePhone *mp)
{
	cout << mp->year << "年  " << mp->memory << "G " << mp->name << endl;
}
int main()
{
	Person *pp = new Person(234,"yar");
	MobilePhone *mp = new MobilePhone;
	pp->show(mp);
	system("pause");
	return 0;
}

友元類

當(dāng)一個類為另一個類的友元時,稱這個類為友元類。 友元類的所有成員函數(shù)都是另一個類中的友元成員。
語法形式:friend [class] 友元類名

  • 類之間的友元關(guān)系不能傳遞
  • 類之間的友元關(guān)系是單向的
  • 友元關(guān)系不能被繼承
class HardDisk
{
public:
	HardDisk();
	friend class Computer;
private:
	int capacity;
	int speed;
	string brand;
};
HardDisk::HardDisk():capacity(128),speed(0),brand("三星"){
}
class Computer
{
public:
	Computer(HardDisk hd);
	void start();
private:
	string userName;
	string name;
	int ram;
	string cpu;
	int osType;
	HardDisk hardDisk;
};
Computer::Computer(HardDisk hd):userName("yar"),name("YAR-PC"),ram(16),cpu("i7-4710"),osType(64)
{
	cout << "正在創(chuàng)建computer..." << endl;
	this->hardDisk = hd;
	this->hardDisk.speed = 5400;
	cout << "硬盤轉(zhuǎn)動...speed = " << this->hardDisk.speed << "轉(zhuǎn)/分鐘" << endl;
}
void Computer::start()
{
	cout << hardDisk.brand << " " << hardDisk.capacity << "G" << hardDisk.speed << "轉(zhuǎn)/分鐘" << endl;
	cout << "筆記本開始運(yùn)行..." << endl;
}
int main()
{
	HardDisk hd;
	Computer cp(hd);
	cp.start();
	system("pause");
	return 0;
}

13.9 類(class)與結(jié)構(gòu)體(struct)的區(qū)別

  • 引入C語言的結(jié)構(gòu)體,是為了保證和c程序的兼容性。
  • c語言中的結(jié)構(gòu)體不允許定義函數(shù)成員,且沒有訪問控制權(quán)限的屬性。
  • c++為結(jié)構(gòu)體引入了成員函數(shù),訪問控制權(quán)限,繼承,多態(tài)等面向?qū)ο筇匦浴?/li>
  • c語言中,空結(jié)構(gòu)體的大小為0,而C++中空結(jié)構(gòu)體大小為1。
  • class中成員默認(rèn)是private,struct中的成員默認(rèn)是public。
  • class繼承默認(rèn)是private繼承,而struct繼承默認(rèn)是public繼承。
  • class可以使用模版,而struct不能。

舉個例子:

//結(jié)構(gòu)體默認(rèn)權(quán)限為public
struct person
{
	void show();
	string name;
	int age;
};
int main()
{
	person p;
	p.name = "heiren";
	p.age = 666;
	p.show();
	cout <<"name="<< p.name <<" age="<< p.age << endl;
	system("pause");
	return 0;
}

將struct改為class,運(yùn)行報(bào)錯。

14、繼承和派生

14.1 繼承和派生概述

繼承就是再一個已有類的基礎(chǔ)上建立一個新類,已有的類稱基類或父類,新建立的類稱為派生類和子類;派生和繼承是一個概念,角度不同而已,繼承是兒子繼承父親的產(chǎn)業(yè),派生是父親把產(chǎn)業(yè)傳承給兒子。

一個基類可以派生出多個派生類,一個派生類可以繼承多個基類

派生類的聲明:

//繼承方式為可選項(xiàng),默認(rèn)為private,還有public,protected
class 派生類名:[繼承方式]基類名
{
	派生類新增加的成員聲明;
};

繼承方式:

  • public-基類的public成員和protected成員的訪問屬性保持不變,私有成員不可見。
  • private-基類的public成員和protected成員成為private成員,只能被派生類的成員函數(shù)直接訪問,私有成員不可見。
  • protected-基類的public成員和protected成員成為protected成員,只能被派生類的成員函數(shù)直接訪問,私有成員不可見。
繼承方式/基類成員public成員protected成員private成員
publicpublicprotected不可見
protectedprotectedprotected不可見
privateprivateprivate不可見

利用using關(guān)鍵字可以改變基類成員再派生類中的訪問權(quán)限;using只能修改基類中public和protected成員的訪問權(quán)限。

class Base
{
public:
	void show();
protected:
	int aa;
	double dd;
};
void Base::show(){
}
class Person:public Base
{
public:
	using Base::aa;//將基類的protected成員變成public
	using Base::dd;//將基類的protected成員變成public
private:
	using Base::show;//將基類的public成員變成private
	string name;
};
int main()
{
	Person *p = new Person();
	p->aa = 12;
	p->dd = 12.3;
	p->show();//出錯
	delete p;
	return 0;
}

派生類的構(gòu)造函數(shù)和析構(gòu)函數(shù)

  • 先執(zhí)行基類的構(gòu)造函數(shù),隨后執(zhí)行派生類的構(gòu)造函數(shù)
  • 先執(zhí)行派生類的析構(gòu)函數(shù),再執(zhí)行基類的析構(gòu)函數(shù)。
  • 派生類的構(gòu)造函數(shù):派生類名(總參數(shù)列表):基類名(基類參數(shù)列表),子對象名1(參數(shù)列表){構(gòu)造函數(shù)體;}
class Base
{
public:
	Base(int, double);
	~Base();
private:
	int aa;
	double dd;
};
Base::Base(int a, double d) :aa(a), dd(d)
{
	cout << "Base Class 構(gòu)造函數(shù)!!!" << endl;
}
Base::~Base()
{
	cout << "Base Class 析構(gòu)函數(shù)!!!" << endl;
}
class Person:public Base
{
public:
	Person(int,double,string);
	~Person();
private:
	string name;
};
Person::Person(int a,double d,string str):Base(a,d),name(str)
{
	cout << "Person Class 構(gòu)造函數(shù)!!!" << endl;
}
Person::~Person()
{
	cout << "Person Class 析構(gòu)函數(shù)!!!" << endl;
}
int main()
{
	cout << "創(chuàng)建Person對象..." << endl;
	Person *p = new Person(1,2,"yar");
	cout << "刪除Person對象...." << endl;
	delete p;
	system("pause");
	return 0;
}

輸出結(jié)果

14.2 多繼承

一個派生類同時繼承多個基類的行為。

多繼承容易讓代碼邏輯復(fù)雜、思路混亂,一直備受爭議,中小型項(xiàng)目中較少使用,后來的 Java、C#、PHP 等干脆取消了多繼承。

多重繼承派生類聲明的一般形式:

class 派生類名:繼承方式1 基類1,繼承方式2 基類2
{
	派生類主體;
};

多重繼承派生類的構(gòu)造函數(shù):

派生類名(總參數(shù)列表):基類名1(基類參數(shù)列表1),基類名2(基類參數(shù)列表2),
子對象名1,...(參數(shù)列表)
{
	構(gòu)造函數(shù)體;
}`

二義性問題:多個基類中有同名成員,出現(xiàn)訪問不唯一的問題。

  • 1.類名::同名成員名;
  • 2.派生類定義同名成員,訪問的就是派生類同名成員。

14.3 虛基類

c++引入虛基類使得派生類再繼承間接共同基類時只保留一份同名成員。

  • 虛繼承的目的是讓某個類做出聲明,承諾愿意共享它的基類。其中,這個被共享的基類就稱為虛基類(Virtual Base Class)。
  • 派生類的 同名成員 比虛基類的 優(yōu)先級更高
    虛基類的聲明:class 派生類名:virtual 繼承方式 基類名
class  A//虛基類
{
protected:
	int a;
};
class B: virtual public A
{
protected:
	int b;
};
class C:virtual public A
{
protected:
	int c;
};
class D:public B,public C
{
protected:
	int d;
	void show()
	{
		b = 123;
		c = 23;
		a = 1;
	}
};
  • 如果 B 或 C 其中的一個類定義了a,也不會有二義性,派生類的a 比虛基類的a 優(yōu)先級更高。
  • 如果 B 和 C 中都定義了 a,那么D直接訪問a 將產(chǎn)生二義性問題。

應(yīng)用:c++中的iostream , istream , ostream,base_io

15、多態(tài)和虛函數(shù)

15.1 向上轉(zhuǎn)型

數(shù)據(jù)類型的轉(zhuǎn)換,編譯器會將小數(shù)部分直接丟掉(不是四舍五入)

int a = 66.9;
printf("%d\n", a);//66
float b = 66;
printf("%f\n", b);//66.000000
  • 只能將將派生類賦值給基類(C++中稱為向上轉(zhuǎn)型): 派生類對象賦值給基類對象、將派生類指針賦值給基類指針、將派生類引用賦值給基類引用
  • 派生類對象賦值給基類對象,舍棄派生類新增的成員;派生類指針賦值給基類指針,沒有拷貝對象的成員,也沒有修改對象本身的數(shù)據(jù),僅僅是改變了指針的指向;派生類引用賦值給基類引用,和指針的一樣。、

上轉(zhuǎn)型后通過基類的對象、指針、引用只能訪問從基類繼承過去的成員(包括成員變量和成員函數(shù)),不能訪問派生類新增的成員

15.2 多態(tài)

不同的對象可以使用同一個函數(shù)名調(diào)用不同內(nèi)容的函數(shù)。

  • 靜態(tài)多態(tài)性-在程序編譯時系統(tǒng)就決定調(diào)用哪個函數(shù),比如函數(shù)重載和靜態(tài)多態(tài)性
  • 動態(tài)多態(tài)性-在程序運(yùn)行過程中動態(tài)確定調(diào)用那個函數(shù),通過虛函數(shù)實(shí)現(xiàn)的。

15.3 虛函數(shù)

實(shí)現(xiàn)程序多態(tài)性的一個重要手段,使用基類對象指針訪問派生類對象的同名函數(shù)。

  • 將基類中的函數(shù)聲明為虛函數(shù),派生類中的同名函數(shù)自動為虛函數(shù)。
  • 聲明形式:virtual 函數(shù)類型 函數(shù)名 (參數(shù)列表);
  • 構(gòu)造函數(shù)不能聲明為虛函數(shù),析構(gòu)函數(shù)可以聲明為虛函數(shù)。
class  A
{
public:
	virtual void show()
	{
		cout << "A show" << endl;
	}
};
class B:  public A
{
public:
	void show()
	{
		cout << "B show" << endl;
	}
};
int main()
{
	B b;
	b.show();//B show
	A *pA = &b;
	pA->show();//B show 如果show方法前沒用virtual聲明為虛函數(shù),這里會輸出A show
	system("pause");
	return 0;
}

15.4 純虛函數(shù)

在基類中不執(zhí)行具體的操作,只為派生類提供統(tǒng)一結(jié)構(gòu)的虛函數(shù),將其聲明為虛函數(shù)。

class  A
{
public:
	virtual void show() = 0;
};
class B:  public A
{
public:
	void show()
	{
		cout << "B show" << endl;
	}
};

抽象類:包含純虛函數(shù)的類稱為抽象類。由于純虛函數(shù)不能被調(diào)用,所以不能利用抽象類創(chuàng)建對象,又稱抽象基類。

16、運(yùn)算符重載

所謂重載,就是賦予新的含義。函數(shù)重載(Function Overloading)可以讓一個函數(shù)名有多種功能,在不同情況下進(jìn)行不同的操作。運(yùn)算符重載(Operator Overloading)也是一個道理,同一個運(yùn)算符可以有不同的功能。

運(yùn)算符重載是通過函數(shù)實(shí)現(xiàn)的,它本質(zhì)上是函數(shù)重載。

允許重載的運(yùn)算符

運(yùn)算符名稱運(yùn)算符
雙目算術(shù)運(yùn)算符+、-、*、、、%
關(guān)系運(yùn)算符==、!=、<、>、<=、>=
邏輯運(yùn)算符||、&&、!
單目運(yùn)算符+、-、*(指針)、&(取地址)
自增自減運(yùn)算符++、–
位運(yùn)算符|、&、-、……、<<、>>
賦值運(yùn)算符=、+=、-=、*=、/=、%=、&=、!=、^=、<<= 、>>=
空間分配和釋放new、delete、new[]、delete[]
其他運(yùn)算符()(函數(shù)調(diào)用) 、->(成員訪問)、->*(成員指針訪問)、,(逗號)、

不允許重載的運(yùn)算符

運(yùn)算符名稱運(yùn)算符
成員訪問運(yùn)算符.
成員指針訪問運(yùn)算符. *
域運(yùn)算符::
長度運(yùn)算符sizeof()
條件運(yùn)算符?:

16.1 定義

重載運(yùn)算符遵循的規(guī)則:

  • 不可以自己定義新的運(yùn)算符,只能對已有的C++運(yùn)算符重載。
  • 不能改變運(yùn)算符運(yùn)算對象的個數(shù)。
  • 不能改變運(yùn)算符的優(yōu)先級和結(jié)合性
  • 應(yīng)與標(biāo)準(zhǔn)類型運(yùn)算功能相似,避免影響可讀性。

一般格式:

函數(shù)類型 operator運(yùn)算符(參數(shù)列表)
{
	函數(shù)體
}
//舉個栗子:定義一個向量類,通過運(yùn)算符重載,可以用+進(jìn)行運(yùn)算。
class Vector3
{
public:
	Vector3();
	Vector3(double x,double y,double z);
public:
	Vector3 operator+(const Vector3 &A)const;
	void display()const;
private:
	double m_x;
	double m_y;
	double m_z;
};
Vector3::Vector3() :m_x(0.0), m_y(0.0), m_z(0.0) {}
Vector3::Vector3(double x, double y,double z) : m_x(x), m_y(y), m_z(z) {}
//運(yùn)算符重載
Vector3 Vector3::operator+(const Vector3 &A) const
{
	Vector3 B;
	B.m_x = this->m_x + A.m_x;
	B.m_y = this->m_y + A.m_y;
	B.m_z = this->m_z + A.m_z;
	return B;
}
void  Vector3::display()const
{
	cout<<"(" << m_x << "," << m_y << "," << m_z << ")" << endl;
}

16.2 形式

運(yùn)算符重載的形式有兩種:重載函數(shù)作為類的成員,重載函數(shù)作為類的友元函數(shù)
根據(jù)運(yùn)算符操作數(shù)的不同:雙目運(yùn)算符作為類成員函數(shù),單目運(yùn)算符作為類的成員函數(shù),雙目運(yùn)算符作為類的友員函數(shù),單目運(yùn)算符作為類的友元函數(shù)。

  • 雙目運(yùn)算符作為友元函數(shù)時需要制定兩個參數(shù)。
  • 運(yùn)算符重載函數(shù)作為類成員函數(shù)可以顯式調(diào)用。
class Vector3
{
public:
	Vector3();
	Vector3(double x,double y,double z);
public:
	Vector3 operator+(const Vector3 &A)const;
	Vector3 operator++();
	friend Vector3 operator-(const Vector3 &v1, const Vector3 &v2);
	friend Vector3 operator--(Vector3 &v);
	void display()const;
private:
	double m_x;
	double m_y;
	double m_z;
};
Vector3::Vector3() :m_x(0.0), m_y(0.0), m_z(0.0) {}
Vector3::Vector3(double x, double y,double z) : m_x(x), m_y(y), m_z(z) {}
//運(yùn)算符重載
Vector3 Vector3::operator+(const Vector3 &A) const
{
	Vector3 B;
	B.m_x = this->m_x + A.m_x;
	B.m_y = this->m_y + A.m_y;
	B.m_z = this->m_z + A.m_z;
	return B;
}
Vector3 Vector3::operator++()
{
	this->m_x ++;
	this->m_y ++;
	this->m_z ++;
	return *this;
}
void  Vector3::display()const
{
	cout<<"(" << m_x << "," << m_y << "," << m_z << ")" << endl;
}
Vector3 operator-(const Vector3 &v1,const Vector3 &v2)
{
	Vector3 B(v1.m_x - v2.m_x, v1.m_y - v2.m_y, v1.m_z - v2.m_z);
	return B;
}
Vector3 operator--( Vector3 &v)
{
	v.m_x--;
	v.m_y--;
	v.m_z --;
	return v;
}
int main()
{
	Vector3 v1(1, 2, 3);
	Vector3 v2(2, 3, 2);
	++v1;//v1.operator++(); 作為類成員函數(shù)可以顯式調(diào)用
	v1.display();
	--v2;
	v2.display();
	Vector3 v3 = v1 + v2;// v1.operator+(v2);作為類成員函數(shù)可以顯式調(diào)用
	v3.display();
	Vector3 v4 = v1 - v2;
	v4.display();
	return 0;
}

16.3 常用運(yùn)算符的重載

1.自增自減:

//前置運(yùn)算符 ++a --a
operator++()
operator--()
operator++(Vector3 &v)
operator--(Vector3 &v)
//后置運(yùn)算符 a-- a++
operator++(int)
operator--(int)
operator++(Vector3 &v,int)
operator--(Vector3 &v,int)

2.賦值運(yùn)算符:

String& String::operator=(String &s)
{
  if(this!=&s)
  {
  		delete[] str;
  		int length = strlen(s.str);
  		str = new char[length+1];
  		strcpy(str,s.str);
  }
  return (*this)
}

3.輸入\輸出運(yùn)算符重載

 friend ostream &operator<<( ostream &output, 
                                       const Vector3 &v )
      { 
         output << "F : " <<v.m_x<< " I : " << v.m_y<<v.m_z;
         return output;            
      }
      friend istream &operator>>( istream  &input, Vector3 &v )
      { 
         input >> v.m_x>> v.m_y>>v.m_z;
         return input;            
      }

16.4 實(shí)現(xiàn)類型轉(zhuǎn)換

  • 不指定函數(shù)類型和參數(shù),返回值的類型由類型名來確定。
  • 類型轉(zhuǎn)換函數(shù)只能作為成員函數(shù),不能作為友元函數(shù)。

類型轉(zhuǎn)換函數(shù)的一般形式:

operator 類型名()
{
	轉(zhuǎn)換語句;
}
class Vector3
{
public:
	Vector3();
	Vector3(double x,double y,double z);
public:
	Vector3 operator+(const Vector3 &A)const;
	Vector3 operator++();
	friend Vector3 operator-(const Vector3 &v1, const Vector3 &v2);
	friend Vector3 operator--(Vector3 &v,int);
	operator double()
	{
		return m_x + m_y + m_z;
	}
	void display()const;
private:
	double m_x;
	double m_y;
	double m_z;
};
int main()
{
	Vector3 v1(1, 2, 3);
	double d = v1;
	cout << d << endl;//6
	return 0;
}

17、IO流

流-一連串連續(xù)不斷的數(shù)據(jù)集合。

17.1 流類和對象

  • 輸入流-從輸入設(shè)備流向內(nèi)存的流。
  • 輸出流-從內(nèi)存流出設(shè)備的流。
  • 內(nèi)存緩沖區(qū)-用來存放流中的數(shù)據(jù)。

輸入輸出流程:鍵盤輸入=》鍵盤緩沖區(qū)=(回車觸發(fā))》程序的輸入緩沖區(qū)=》‘>>’提取數(shù)據(jù)
                        輸出緩沖區(qū)=(緩沖滿或endl)》‘<<’送到 顯示器顯示
輸入/輸出流類:
iostream:ios ,istream,ostream,iostream
fstream:ifstream,ofstream,fstream
strstream:istrstream,ostrstream,strstream

  • istream 是用于輸入的流類,cin 就是該類的對象。
  • ostream 是用于輸出的流類,cout 就是該類的對象。
  • ifstream 是用于從文件讀取數(shù)據(jù)的類。
  • ofstream 是用于向文件寫入數(shù)據(jù)的類。
  • iostream 是既能用于輸入,又能用于輸出的類。
  • fstream 是既能從文件讀取數(shù)據(jù),又能向文件寫入數(shù)據(jù)的類。
  • istrstream 輸入字符串類
  • ostrstream 輸出字符串類
  • strstream 輸入輸出字符串流類

17.2 標(biāo)準(zhǔn)輸入輸出流

C++的輸入/輸出流庫(iostream)中定義了4個標(biāo)準(zhǔn)流對象:cin(標(biāo)準(zhǔn)輸入流-鍵盤),cout(標(biāo)準(zhǔn)輸出流-屏幕),cerr(標(biāo)準(zhǔn)錯誤流-屏幕),clog(標(biāo)準(zhǔn)錯誤流-屏幕)

  • cerr 不使用緩沖區(qū),直接向顯示器輸出信息;而輸出到 clog 中的信息會先被存放到緩沖區(qū),緩沖區(qū)滿或者刷新時才輸出到屏幕。
  • cout 是 ostream 類的對象,ostream 類的無參構(gòu)造函數(shù)和復(fù)制構(gòu)造函數(shù)都是私有的,所以無法定義 ostream 類的對象。
  • 使用>>提取數(shù)據(jù)時,系統(tǒng)會跳過空格,制表符,換行符等空白字符。所以一組變量輸入值時,可用這些隔開。
  • 輸入字符串,也是跳過空白字符,會在串尾加上字符串結(jié)束標(biāo)志\0。
int  x;
double y;
cin>>x>>y;
//輸入 22 66.0  兩個數(shù)之間可以用空格、制表符和回車分隔數(shù)據(jù)
char str[10];
cin>>str;//hei ren  字符串中只有hei\0

輸入流中的成員函數(shù)

get函數(shù):cin.get(),cin.get(ch)(成功返回非0值,否則返回0),cin.get(字符數(shù)組(或字符指針),字符個數(shù)n,終止字符)

char c = cin.get();//獲取一個字符
while ((c = cin.get()) != EOF)//循環(huán)讀取,直到換行
{
	cout << c;
}
char ch;
cin.get(ch);
while (cin.get(ch))//讀取成功循環(huán)
{
	cout << ch;
}
char arr[5];
cin.get(arr, 5, '\n');//輸入 heiren  結(jié)果 heir\0

getline函數(shù):cin.getline(字符數(shù)組(或字符指針),字符個數(shù)n,終止標(biāo)志字符)

讀取字符知道終止字符或者讀取n-1個字符,賦值給指定字符數(shù)組(或字符指針)

char arr0[30],arr1[30],arr2[40];
cin>>arr0;//遇到空格、制表符或回車結(jié)束  "Heiren"
cin.getline(arr1,30);//字符數(shù)最多為29個,遇到回車結(jié)束 " Hello World"
cin.getline(arr2,40,'*');//最多為39個,遇到*結(jié)束 "yar"
//輸入 Heiren Hello World
//yar*123

cin.peek() 不會跳過輸入流中的空格、回車符。在輸入流已經(jīng)結(jié)束的情況下,cin.peek() 返回 EOF。

ignore(int n =1, int delim = EOF)

int n;
 cin.ignore(5, 'Y');//跳過前5個字符或Y之前的字符,‘Y'優(yōu)先
 cin >> n;
 //輸入1234567  ->  67    1234567Y345->345
//輸入2020.2.23
int year,month,day;
cin >> year ;
cin.ignore() >> month ; //用ignore跳過 '.'
 cin.ignore() >> day;
cin.ignore();   //跳過行末 '\n'
 cout<< setfill('0') << setw(2) << month ;//設(shè)置填充字符'\0',輸出寬度2
 cout << "-" << setw(2) << day << "-" << setw(4) << year << endl;

putback(char c),可以將一個字符插入輸入流的最前面。

輸出流對象

  • 插入endl-輸出所有數(shù)據(jù),插入換行符,清空緩沖區(qū)
  • \n-輸出換行,不清空緩沖區(qū)
  • cout.put(參數(shù)) 輸出單個字符(可以時字符也可以是ASII碼)

格式化輸出

iomanip 中定義的流操作算子:

*不是算子的一部分,星號表示在沒有使用任何算子的情況下,就等效于使用了該算子,例如,在默認(rèn)情況下,整數(shù)是用十進(jìn)制形式輸出的,等效于使用了 dec 算子

流操縱算子作 用
*dec以十進(jìn)制形式輸出整數(shù) 常用
hex以十六進(jìn)制形式輸出整數(shù)
oct以八進(jìn)制形式輸出整數(shù)
fixed以普通小數(shù)形式輸出浮點(diǎn)數(shù)
scientific以科學(xué)計(jì)數(shù)法形式輸出浮點(diǎn)數(shù)
left左對齊,即在寬度不足時將填充字符添加到右邊
*right右對齊,即在寬度不足時將填充字符添加到左邊
setbase(b)設(shè)置輸出整數(shù)時的進(jìn)制,b=8、10 或 16
setw(w)指定輸出寬度為 w 個字符,或輸人字符串時讀入 w 個字符
setfill©在指定輸出寬度的情況下,輸出的寬度不足時用字符 c 填充(默認(rèn)情況是用空格填充)
setprecision(n)設(shè)置輸出浮點(diǎn)數(shù)的精度為 n。在使用非 fixed 且非 scientific 方式輸出的情況下,n 即為有效數(shù)字最多的位數(shù),如果有效數(shù)字位數(shù)超過 n,則小數(shù)部分四舍五人,或自動變?yōu)榭茖W(xué)計(jì) 數(shù)法輸出并保留一共 n 位有效數(shù)字。在使用 fixed 方式和 scientific 方式輸出的情況下,n 是小數(shù)點(diǎn)后面應(yīng)保留的位數(shù)。
setiosflags(flag)將某個輸出格式標(biāo)志置為 1
resetiosflags(flag)將某個輸出格式標(biāo)志置為 0
boolapha把 true 和 false 輸出為字符串 不常用
*noboolalpha把 true 和 false 輸出為 0、1
showbase輸出表示數(shù)值的進(jìn)制的前綴
*noshowbase不輸出表示數(shù)值的進(jìn)制.的前綴
showpoint總是輸出小數(shù)點(diǎn)
*noshowpoint只有當(dāng)小數(shù)部分存在時才顯示小數(shù)點(diǎn)
showpos在非負(fù)數(shù)值中顯示 +
*noshowpos在非負(fù)數(shù)值中不顯示 +
*skipws輸入時跳過空白字符
noskipws輸入時不跳過空白字符
uppercase十六進(jìn)制數(shù)中使用 A~E。若輸出前綴,則前綴輸出 0X,科學(xué)計(jì)數(shù)法中輸出 E
*nouppercase十六進(jìn)制數(shù)中使用 a~e。若輸出前綴,則前綴輸出 0x,科學(xué)計(jì)數(shù)法中輸出 e。
internal數(shù)值的符號(正負(fù)號)在指定寬度內(nèi)左對齊,數(shù)值右對 齊,中間由填充字符填充。

流操作算子使用方法:cout << hex << 12 << "," << 24;//c,18

setiosflags() 算子

setiosflags() 算子實(shí)際上是一個庫函數(shù),它以一些標(biāo)志作為參數(shù),這些標(biāo)志可以是在 iostream 頭文件中定義的以下幾種取值,它們的含義和同名算子一樣。

標(biāo) 志作 用
ios::left輸出數(shù)據(jù)在本域?qū)挿秶鷥?nèi)向左對齊
ios::right輸出數(shù)據(jù)在本域?qū)挿秶鷥?nèi)向右對齊
ios::internal數(shù)值的符號位在域?qū)拑?nèi)左對齊,數(shù)值右對齊,中間由填充字符填充
ios::dec設(shè)置整數(shù)的基數(shù)為 10
ios::oct設(shè)置整數(shù)的基數(shù)為 8
ios::hex設(shè)置整數(shù)的基數(shù)為 16
ios::showbase強(qiáng)制輸出整數(shù)的基數(shù)(八進(jìn)制數(shù)以 0 開頭,十六進(jìn)制數(shù)以 0x 打頭)
ios::showpoint強(qiáng)制輸出浮點(diǎn)數(shù)的小點(diǎn)和尾數(shù) 0
ios::uppercase在以科學(xué)記數(shù)法格式 E 和以十六進(jìn)制輸出字母時以大寫表示
ios::showpos對正數(shù)顯示“+”號
ios::scientific浮點(diǎn)數(shù)以科學(xué)記數(shù)法格式輸出
ios::fixed浮點(diǎn)數(shù)以定點(diǎn)格式(小數(shù)形式)輸出
ios::unitbuf每次輸出之后刷新所有的流
ios::stdio每次輸出之后清除 stdout, stderr

多個標(biāo)志可以用|運(yùn)算符連接,表示同時設(shè)置。例如:

cout << setiosflags(ios::scientific|ios::showpos) << 12.34;//+1.234000e+001

如果兩個相互矛盾的標(biāo)志同時被設(shè)置,結(jié)果可能就是兩個標(biāo)志都不起作用,應(yīng)該用 resetiosflags 清除原先的標(biāo)志

cout << setiosflags(ios::fixed) << 12.34 << endl;
cout << resetiosflags(ios::fixed) << setiosflags(ios::scientific | ios::showpos) << 12.34 << endl;

ostream 類中的成員函數(shù):

成員函數(shù)作用相同的流操縱算子說明
precision(n)setprecision(n)設(shè)置輸出浮點(diǎn)數(shù)的精度為 n。
width(w)setw(w)指定輸出寬度為 w 個字符。
fill©setfill ©在指定輸出寬度的情況下,輸出的寬度不足時用字符 c 填充(默認(rèn)情況是用空格填充)。
setf(flag)setiosflags(flag)將某個輸出格式標(biāo)志置為 1。
unsetf(flag)resetiosflags(flag)將某個輸出格式標(biāo)志置為 0。

setf 和 unsetf 函數(shù)用到的flag,與 setiosflags 和 resetiosflags 用到的完全相同。

cout.setf(ios::scientific);
cout.precision(8);
cout << 12.23 << endl;//1.22300000e+001

18、文件操作

文件-指存儲在外部介質(zhì)上的數(shù)據(jù)集合,文件按照數(shù)據(jù)的組織形式不一樣,分為兩種:ASCII文件(文本/字符),二進(jìn)制文件(內(nèi)部格式/字節(jié))

ASCII文件輸出還是二進(jìn)制文件,數(shù)據(jù)形式一樣,對于數(shù)值數(shù)據(jù),輸出不同

18.1 文件類和對象

C++ 標(biāo)準(zhǔn)類庫中有三個類可以用于文件操作,它們統(tǒng)稱為文件流類。這三個類是:

  • ifstream:輸入流類,用于從文件中讀取數(shù)據(jù)。
  • ofstream:輸出流類,用于向文件中寫人數(shù)據(jù)。
  • fstream:輸入/輸出流類,既可用于從文件中讀取數(shù)據(jù),又可用于 向文件中寫人數(shù)據(jù)。

文件流對象定義:

#include <fstream>
ifstream in;
ofstream out;
fstream inout;

18.2 打開文件

打開文件的目的:建立對象與文件的關(guān)聯(lián),指明文件使用方式

打開文件的兩種方式:open函數(shù)和構(gòu)造函數(shù)

open函數(shù):void open(const char* szFileName, int mode);

模式標(biāo)記適用對象作用
ios::inifstream fstream打開文件用于讀取數(shù)據(jù)。如果文件不存在,則打開出錯。
ios::outofstream fstream打開文件用于寫入數(shù)據(jù)。如果文件不存在,則新建該文件;如 果文件原來就存在,則打開時清除原來的內(nèi)容。
ios::appofstream fstream打開文件,用于在其尾部添加數(shù)據(jù)。如果文件不存在,則新建該文件。
ios::ateifstream打開一個已有的文件,并將文件讀指針指向文件末尾(讀寫指 的概念后面解釋)。如果文件不存在,則打開出錯。
ios:: truncofstream單獨(dú)使用時與 ios:: out 相同。
ios::binaryifstream ofstream fstream以二進(jìn)制方式打開文件。若不指定此模式,則以文本模式打開。
ios::in | ios::outfstream打開已存在的文件,既可讀取其內(nèi)容,也可向其寫入數(shù)據(jù)。文件剛打開時,原有內(nèi)容保持不變。如果文件不存在,則打開出錯。
ios::in | ios::outofstream打開已存在的文件,可以向其寫入數(shù)據(jù)。文件剛打開時,原有內(nèi)容保持不變。如果文件不存在,則打開出錯。
ios::in | ios::out | ios::truncfstream打開文件,既可讀取其內(nèi)容,也可向其寫入數(shù)據(jù)。如果文件本來就存在,則打開時清除原來的內(nèi)容;如果文件不存在,則新建該文件。

ios::binary 可以和其他模式標(biāo)記組合使用,例如:

  • ios::in | ios::binary表示用二進(jìn)制模式,以讀取的方式打開文件。
  • ios::out | ios::binary表示用二進(jìn)制模式,以寫入的方式打開文件。

流類的構(gòu)造函數(shù)

eg:ifstream::ifstream (const char* szFileName, int mode = ios::in, int);

#include <iostream>
#include <fstream>
using namespace std;
int main()
{
    ifstream inFile("c:\\tmp\\test.txt", ios::in);
    if (inFile)
        inFile.close();
    else
        cout << "test.txt doesn't exist" << endl;
    ofstream oFile("test1.txt", ios::out);
    if (!oFile)
        cout << "error 1";
    else
        oFile.close();
    fstream oFile2("tmp\\test2.txt", ios::out | ios::in);
    if (!oFile2)
        cout << "error 2";
    else
        oFile.close();
    return 0;
}

18.3 文本文件的讀寫

對于文本文件,可以使用 cin、cout 讀寫。
eg:編寫一個程序,將文件 i.txt 中的整數(shù)倒序輸出到 o.txt。(12 34 56 78 90 -> 90 78 56 34 12)

#include <iostream>
#include <fstream>
using namespace std;
int arr[100];
int main()
{
	int num = 0;
	ifstream inFile("i.txt", ios::in);//文本模式打開
	if (!inFile)
		return 0;//打開失敗
	ofstream outFile("o.txt",ios::out);
	if (!outFile)
	{
		outFile.close();
		return 0;
	}
	int x;
	while (inFile >> x)
		arr[num++] = x;
	for (int i = num - 1; i >= 0; i--)
		outFile << arr[i] << " ";
	inFile.close();
	outFile.close();
	return 0;
}

18.4 二進(jìn)制文件的讀寫

  • 用文本方式存儲信息不但浪費(fèi)空間,而且不便于檢索。
  • 二進(jìn)制文件中,信息都占用 sizeof(對象名) 個字節(jié);文本文件中類的成員數(shù)據(jù)所占用的字節(jié)數(shù)不同,占用空間一般比二進(jìn)制的大。

ostream::write 成員函數(shù):ostream & write(char* buffer, int count);

class Person
{
public:
	char m_name[20];
	int m_age;
};
int main()
{
	Person p;
	ofstream outFile("o.bin", ios::out | ios::binary);
	while (cin >> p.m_name >> p.m_age)
		outFile.write((char*)&p, sizeof(p));//強(qiáng)制類型轉(zhuǎn)換
	outFile.close();
	//heiren 燙燙燙燙燙燙啼  
	return 0;
}

istream::read 成員函數(shù):istream & read(char* buffer, int count);

Person p;
ifstream inFile("o.bin", ios::in | ios::binary); //二進(jìn)制讀方式打開
if (!inFile)
	return 0;//打開文件失敗
while (inFile.read((char *)&p, sizeof(p)))
	cout << p.m_name << " " << p.m_age << endl;
inFile.close();

文件流類的 put 和 get 成員函數(shù)

#include <iostream>
#include <fstream>
using namespace std;
int main()
{
    ifstream inFile("a.txt", ios::binary | ios::in); 
    if (!inFile)
        return 0;
    ofstream outFile("b.txt", ios::binary | ios::out);
    if (!outFile) 
    {
        inFile.close();
        return 0;
    }
    char c;
    while (inFile.get(c))  //每讀一個字符
        outFile.put(c);  //寫一個字符
    outFile.close();
    inFile.close();
    return 0;
}

一個字節(jié)一個字節(jié)地讀寫,不如一次讀寫一片內(nèi)存區(qū)域快。每次讀寫的字節(jié)數(shù)最好是 512 的整數(shù)倍

18.5 移動和獲取文件讀寫指針

  • ifstream 類和 fstream 類有 seekg 成員函數(shù),可以設(shè)置文件讀指針的位置;
  • ofstream 類和 fstream 類有 seekp 成員函數(shù),可以設(shè)置文件寫指針的位置。
  • ifstream 類和 fstream 類還有 tellg 成員函數(shù),能夠返回文件讀指針的位置;
  • ofstream 類和 fstream 類還有 tellp 成員函數(shù),能夠返回文件寫指針的位置。

函數(shù)原型

ostream & seekp (int offset, int mode);
istream & seekg (int offset, int mode);
//mode有三種:ios::beg-開頭往后offset(>=0)字節(jié) ios::cur-當(dāng)前往前(<=0)/后(>=0)offset字節(jié) ios::end-末尾往前(<=0)offect字節(jié)
int tellg();
int tellp();
//seekg 函數(shù)將文件讀指針定位到文件尾部,再用 tellg 函數(shù)獲取文件讀指針的位置,此位置即為文件長度

舉個栗子:折半查找文件,name等于“Heiren”

#include <iostream>
#include <fstream>
//#include <vector>
//#include<cstring>
using namespace std;
class Person
{
public:
	char m_name[20];
	int m_age;
};
int main()
{
	Person p;
	ifstream ioFile("p.bin", ios::in | ios::out);//用既讀又寫的方式打開
	if (!ioFile) 
		return 0;
	ioFile.seekg(0, ios::end); //定位讀指針到文件尾部,以便用以后tellg 獲取文件長度
	int L = 0, R; // L是折半查找范圍內(nèi)第一個記錄的序號
				  // R是折半查找范圍內(nèi)最后一個記錄的序號
	R = ioFile.tellg() / sizeof(Person) - 1;
	do {
		int mid = (L + R) / 2; 
		ioFile.seekg(mid *sizeof(Person), ios::beg); 
		ioFile.read((char *)&p, sizeof(p));
		int tmp = strcmp(p.m_name, "Heiren");
		if (tmp == 0)
		{ 
			cout << p.m_name << " " << p.m_age;
			break;
		}
		else if (tmp > 0) 
			R = mid - 1;
		else  
			L = mid + 1;
	} while (L <= R);
	ioFile.close();
	system("pause");
	return 0;
}

18.6 文本文件和二進(jìn)制文件打開方式的區(qū)別

  • UNIX/Linux 平臺中,用文本方式或二進(jìn)制方式打開文件沒有任何區(qū)別。
  • 在 UNIX/Linux 平臺中,文本文件以\n(ASCII 碼為 0x0a)作為換行符號;而在 Windows 平臺中,文本文件以連在一起的\r\n(\r的 ASCII 碼是 0x0d)作為換行符號。
  • 在 Windows 平臺中,如果以文本方式打開文件,當(dāng)讀取文件時,系統(tǒng)會將文件中所有的\r\n轉(zhuǎn)換成一個字符\n,如果文件中有連續(xù)的兩個字節(jié)是 0x0d0a,則系統(tǒng)會丟棄前面的 0x0d 這個字節(jié),只讀入 0x0a。當(dāng)寫入文件時,系統(tǒng)會將\n轉(zhuǎn)換成\r\n寫入。

用二進(jìn)制方式打開文件總是最保險的。

19、泛型和模板

  • 泛型程序設(shè)計(jì)在實(shí)現(xiàn)時不指定具體要操作的數(shù)據(jù)的類型的程序設(shè)計(jì)方法的一種算法,指的是算法只要實(shí)現(xiàn)一遍,就能適用于多種數(shù)據(jù)類型,優(yōu)勢在于代碼復(fù)用,減少重復(fù)代碼的編寫。
  • 模板是泛型的基礎(chǔ),是創(chuàng)建泛型類或函數(shù)的藍(lán)圖或公式。

19.1 函數(shù)模板

函數(shù)模板的一般形式:

template<class T>或template<typename T>
函數(shù)類型 函數(shù)名(參數(shù)列表)
{
	函數(shù)體;
}
template<class T1,class T2,...>//class可以換成typename
函數(shù)類型 函數(shù)名(參數(shù)列表)
{
	函數(shù)體;
}
//舉個栗子
template<class T> T max(T a, T b)
{
	return a > b ? a : b;
}
int main()
{
	cout <<"max value is "<< max(12,34) << endl;//34
	cout << "max value is " << max(12.4, 13.6) << endl;//13.6
	cout << "max value is " << max(12.4, 13) << endl;//error 沒有與參數(shù)列表匹配的 函數(shù)模板 "max" 實(shí)例參數(shù)類型為:(double, int)
	return 0;
}

19.2 類模板

聲明了類模板,就可以將類型參數(shù)用于類的成員函數(shù)和成員變量了。換句話說,原來使用 int、float、char 等內(nèi)置類型的地方,都可以用類型參數(shù)來代替。
類模板的一般形式:

template<class T>//class可以換成typename 模板頭
class 類名
{
	函數(shù)定義;
};
//多個類型參數(shù)和函數(shù)模板類似,逗號隔開

舉個栗子:

template<typename T1, typename T2>  // 模板頭  沒有分號
class Point {
public:
	Point(T1 x, T2 y) : x(x), y(y) { }
public:
	T1 getX() const;  //成員函數(shù)后加const,聲明該函數(shù)內(nèi)部不會改變成員變量的值
	void setX(T1 x);  
	T2 getY() const;  
	void setY(T2 y);  
private:
	T1 x;  
	T2 y;  
};
template<typename T1, typename T2>  //模板頭
T1 Point<T1, T2>::getX() const  {
	return x;
}
template<typename T1, typename T2>
void Point<T1, T2>::setX(T1 x) {
	x = x;
}
template<typename T1, typename T2>
T2 Point<T1, T2>::getY() const {
	return y;
}
template<typename T1, typename T2>
void Point<T1, T2>::setY(T2 y) {
	y = y;
}
int main()
{
	Point<int, double> p1(66, 20.5);
	Point<int, char*> p2(10, "東經(jīng)33度");
	Point<char*, char*> *p3 = new Point<char*, char*>("西經(jīng)12度", "北緯66度");
	cout << "x=" << p1.getX() << ", y=" << p1.getY() << endl;
	cout << "x=" << p2.getX() << ", y=" << p2.getY() << endl;
	cout << "x=" << p3->getX() << ", y=" << p3->getY() << endl;
	return 0;
}

19.3 typename 和 class 的區(qū)別

在模板引入 c++ 后,采用class來定義模板參數(shù)類型,后來為了避免 class 在聲明類和模板的使用可能給人帶來混淆,所以引入了 typename 這個關(guān)鍵字。

  • 模板定義語法中關(guān)鍵字 class 與 typename 的作用完全一樣。
  • 不同的是typename 還有另外一個作用為:使用嵌套依賴類型(nested depended name)
class MyClass
{
public:
		typedef int LengthType;
		LengthType getLength() const
		{
			return this->length;
		}
		void setLength(LengthType length)
		{
			this->length = length;
		}
private:
	LengthType length;
};
template<class T>
void MyMethod(T myclass)
{
	//告訴 c++ 編譯器,typename 后面的字符串為一個類型名稱,而不是成員函數(shù)或者成員變量
	typedef typename T::LengthType LengthType; //
	LengthType length = myclass.getLength();
	cout << "length = " <<length<< endl;
}
int main()
{
	MyClass my;
	my.setLength(666);
	MyMethod(my);//length = 666
	return 0;
}

19.4 強(qiáng)弱類型語言和c++模板的那點(diǎn)貓膩

計(jì)算機(jī)編程語言可以根據(jù)在 "定義變量時是否要顯式地指明數(shù)據(jù)類型"可以分為強(qiáng)類型語言和弱類型語言。

強(qiáng)類型語言-在定義變量時需要顯式地指明數(shù)據(jù)類型,為變量指明某種數(shù)據(jù)類型后就不能賦予其他類型的數(shù)據(jù)了,除非經(jīng)過強(qiáng)制類型轉(zhuǎn)換或隱式類型轉(zhuǎn)換。典型的強(qiáng)類型語言有 C/C++、Java、C# 等。

int a = 123;  //不轉(zhuǎn)換
a = 12.89;  //隱式轉(zhuǎn)換 12(舍去小數(shù)部分)
a = (int)"heiren,HelloWorld";  //強(qiáng)制轉(zhuǎn)換(得到字符串的地址) 不同類型之間轉(zhuǎn)換需要強(qiáng)制
//Java 對類型轉(zhuǎn)換的要求比 C/C++ 更為嚴(yán)格,隱式轉(zhuǎn)換只允許由低向高轉(zhuǎn),由高向低轉(zhuǎn)必須強(qiáng)制轉(zhuǎn)換。
int a = 100;  //不轉(zhuǎn)換
a = (int)12.34;  //強(qiáng)制轉(zhuǎn)換(直接舍去小數(shù)部分,得到12)

弱類型語言-在定義變量時不需要顯式地指明數(shù)據(jù)類型,編譯器(解釋器)會根據(jù)賦給變量的數(shù)據(jù)自動推導(dǎo)出類型,并且可以賦給變量不同類型的數(shù)據(jù)。典型的弱類型語言有 JavaScript、Python、PHP、Ruby、Shell、Perl 等。

var a = 100;  //賦給整數(shù)
a = 12.34;  //賦給小數(shù)
a = "heiren,HelloWorld";  //賦給字符串
a = new Array("JavaScript","React","JSON");  //賦給數(shù)組

強(qiáng)類型語言在編譯期間就能檢測某個變量的操作是否正確,因?yàn)樽兞康念愋褪冀K哦都市確定的,加快了程序的運(yùn)行;對于弱類型的語言,變量的類型可以隨時改變,編譯器在編譯期間能確定變量的類型,只有等到程序運(yùn)行后、賦值后才能確定變量當(dāng)前是什么類型,所以傳統(tǒng)的編譯對弱類型語言意義不大。

  • 解釋型語言-弱類型往往是解釋型語言,邊執(zhí)行邊編譯
  • 編譯型語言-先編譯后執(zhí)行。

強(qiáng)類型語言較為嚴(yán)謹(jǐn),在編譯時就能發(fā)現(xiàn)很多錯誤,適合開發(fā)大型的、系統(tǒng)級的、工業(yè)級的項(xiàng)目;而弱類型語言較為靈活,編碼效率高,部署容易,學(xué)習(xí)成本低,在 Web 開發(fā)中大顯身手。另外,強(qiáng)類型語言的 IDE 一般都比較強(qiáng)大,代碼感知能力好,提示信息豐富;而弱類型語言一般都是在編輯器中直接書寫代碼。

 C++模板退出的動力來源是對數(shù)據(jù)結(jié)構(gòu)的封裝:數(shù)據(jù)結(jié)構(gòu)關(guān)注的是數(shù)據(jù)的存儲以及對其的增刪改查操作,C++開發(fā)者們想封裝這些結(jié)構(gòu),但是這些結(jié)構(gòu)中數(shù)據(jù)成分的類型無法提前預(yù)測,于是模板誕生了。

STL(Standard Template Library,標(biāo)準(zhǔn)模板庫)就是c++對數(shù)據(jù)結(jié)構(gòu)封裝后的稱呼。

20、命名空間和異常處理

20.1 命名空間

    命名空間實(shí)際上是由用戶自己命名的一塊內(nèi)存區(qū)域,用戶可以根據(jù)需要指定一個有名字的空間區(qū)域,每個命名空間都有一個作用域,將一些全局實(shí)體放在該命名空間中,就與其他全局實(shí)體分割開來。

命名空間定義的一般形式:

namespace [命名空間名]//名省略時,表示無名的命名空間
{
	命名空間成員;
}

命名空間成員的引用:命名空間名::命名空間成員名
使用命名空間別名:namespace 別名 = 命名空間名
使用using聲明命名空間成員的格式:using 命名空間名::命名空間成員名;
使用using聲明命名空間的全部成員:using namespace 命名空間名;

  • using聲明后,在using語句所在的作用域中使用該命名空間成員時,不必再用命名空間名加以限定。
  • 標(biāo)準(zhǔn)C++庫的所有標(biāo)識符(包括函數(shù)、類、對象和類模板)都是在一個名為std的命名空間中定義的。
  • 無名的命名空間,只在本文件的作用域內(nèi)有效。

20.2 異常處理

    異常就是程序在執(zhí)行過程中,由于使用環(huán)境變化和用戶操作等原因產(chǎn)生的錯誤,從而影響程序的運(yùn)行。

  • 程序中常見的錯誤:語法錯誤,運(yùn)行錯誤
  • 異常處理機(jī)制的組成:檢查異常(try)、拋出異常(throw)、捕獲并處理異常(catch)

異常處理語句:

  • 被檢查的語句必須放在try后面的{}中,否則不起作用,{}不能省略。
  • 進(jìn)行異常處理的語句必須放在catch后面的{}中,catch后()中的異常信息類型不能省略,變量名可以省略。
  • catch語句塊不能單獨(dú)使用,必須和try語句塊作為整體出現(xiàn)。
  • 在try-catch結(jié)構(gòu)中,只能有一個try,但可以有多個catch.
  • catch(…)通常放在最后,可以捕獲任何類型的異常信息。
try
{
	被檢查的語句(可以有多個throw語句);
}
catch(異常信息類型 [變量名])
{
}

throw語句:thow 變量或表達(dá)式;

throw放在try中,也可以單獨(dú)使用,向上一層函數(shù)尋找try-catch,沒有找到系統(tǒng)就會調(diào)用系統(tǒng)函數(shù)terminate,使程序終止運(yùn)行。

try
{
	throw 123;
}
catch (int a)
{
	cout << a << endl;//123
}

c++中沒有finally

類的異常處理,當(dāng)在try中定義了類對象,在try中拋出異常前創(chuàng)建的對象將被自動釋放。
異常規(guī)范-描述了一個函數(shù)允許拋出那些異常類型。

  • 異常規(guī)范應(yīng)同時出現(xiàn)在函數(shù)聲明和函數(shù)定義中。
  • 如果沒有異常規(guī)范,可以拋出任何類型的異常。

異常規(guī)范的一般形式:函數(shù)類型 函數(shù)名(參數(shù)類型)throw ([異常類型1,異常類型2,...])

float fun(float float)throw(int,float,double);

C++標(biāo)準(zhǔn)異常

在這里插入圖片描述

異常描述
std::exception該異常是所有標(biāo)準(zhǔn) C++ 異常的父類。
std::bad_alloc該異常可以通過 new 拋出。
std::bad_cast該異??梢酝ㄟ^ dynamic_cast 拋出。
std::bad_exception這在處理 C++ 程序中無法預(yù)期的異常時非常有用。
std::bad_typeid該異??梢酝ㄟ^ typeid 拋出。
std::logic_error理論上可以通過讀取代碼來檢測到的異常。
std::domain_error當(dāng)使用了一個無效的數(shù)學(xué)域時,會拋出該異常。
std::invalid_argument當(dāng)使用了無效的參數(shù)時,會拋出該異常。
std::length_error當(dāng)創(chuàng)建了太長的 std::string 時,會拋出該異常。
std::out_of_range該異??梢酝ㄟ^方法拋出,例如 std::vector 和 std::bitset<>::operator。
std::runtime_error理論上不可以通過讀取代碼來檢測到的異常。
std::overflow_error當(dāng)發(fā)生數(shù)學(xué)上溢時,會拋出該異常。
std::range_error當(dāng)嘗試存儲超出范圍的值時,會拋出該異常。
std::underflow_error當(dāng)發(fā)生數(shù)學(xué)下溢時,會拋出該異常。

21、STL

C++標(biāo)準(zhǔn)模板庫(Standard Template Library,STL)是泛型程序設(shè)計(jì)最成功的實(shí)例。STL是一些常用數(shù)據(jù)結(jié)構(gòu)和算法的模板的集合,由Alex Stepanov主持開發(fā),于1998年被加入C++標(biāo)準(zhǔn)。

C++ 標(biāo)準(zhǔn)模板庫的核心包括三大組件:容器,算法,迭代器

21.1 容器

順序容器:可變長動態(tài)數(shù)組Vector、雙端隊(duì)列deque、雙向鏈表list
關(guān)聯(lián)容器:set、multliset、map、multimap

關(guān)聯(lián)容器內(nèi)的元素是排序的,所以查找時具有非常好的性能。
容器適配起:棧stack、隊(duì)列queu、優(yōu)先隊(duì)列priority_queue

所有容器具有的函數(shù):

int size();
bool empty();

順序容器和關(guān)聯(lián)容器函數(shù):

begin()
end()
rbegin()
erase(...)
clear()

順序容器獨(dú)有的函數(shù):

front()
back()
push_back();
pop_back();
insert(...);

21.2 迭代器

迭代器是一種檢查容器內(nèi)元素并遍歷元素的數(shù)據(jù)類型。C++更趨向于使用迭代器而不是下標(biāo)操作,因?yàn)闃?biāo)準(zhǔn)庫為每一種標(biāo)準(zhǔn)容器(如vector)定義了一種迭代器類型,而只用少數(shù)容器(如vector)支持下標(biāo)操作訪問容器元素。按照定義方式分為以下四種。
1.正向迭代器:容器類名::iterator 迭代器名;
2.常量正向迭代器:容器類名::const_iterator 迭代器名;
3.反向迭代器:容器類名::reverse_iterator 迭代器名;
4.常量反向迭代器:容器類名::const_reverse_iterator 迭代器名

21.3 算法

    STL 提供能在各種容器中通用的算法(大約有70種),如插入、刪除、查找、排序等。算法就是函數(shù)模板。算法通過迭代器來操縱容器中的元素。
    STL 中的大部分常用算法都在頭文件 algorithm 中定義。此外,頭文件 numeric 中也有一些算法。
    許多算法操作的是容器上的一個區(qū)間(也可以是整個容器),因此需要兩個參數(shù),一個是區(qū)間起點(diǎn)元素的迭代器,另一個是區(qū)間終點(diǎn)元素的后面一個元素的迭代器。

會改變其所作用的容器。例如:

  • copy:將一個容器的內(nèi)容復(fù)制到另一個容器。
  • remove:在容器中刪除一個元素。
  • random_shuffle:隨機(jī)打亂容器中的元素。
  • fill:用某個值填充容器。

不會改變其所作用的容器。例如:

  • find:在容器中查找元素。
  • count_if:統(tǒng)計(jì)容器中符合某種條件的元素的個數(shù)。
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
int main()  {
   vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4); //1,2,3,4
	vector<int>::iterator p;
	p = find(v.begin(), v.end(), 3); //在v中查找3 若找不到,find返回 v.end()
	if (p != v.end())
		cout << "1) " << *p << endl; //找到了
	p = find(v.begin(), v.end(), 9);
	if (p == v.end())
		cout << "not found " << endl; //沒找到
	p = find(v.begin() + 1, v.end() - 1, 4); //在2,3 這兩個元素中查找4
	cout << "2) " << *p << endl; //沒找到,指向下一個元素4
	int arr[10] = { 10,20,30,40 };
	int * pp = find(arr, arr + 4, 20);
	if (pp == arr + 4)
		cout << "not found" << endl;
	else
		cout << "3) " << *pp << endl;
	return 0;
}

22、總結(jié)

    終于寫完了,因?yàn)樾鹿谝咔椋诩腋綦x了一個多月,閑來無事,寫寫博客來總結(jié)一下自己之前學(xué)的知識;零零散散大約花了一周左右的時間,該篇文章面向C++的基礎(chǔ)知識,有許多詳細(xì),深層的沒有寫進(jìn)去。后面有時間會寫模板,STL,C++11新特性,Boost庫等的文章,剛開始寫博客,文章中有錯誤或有遺漏的知識點(diǎn),請大佬們指出。

相關(guān)文章

最新評論