C/C++ 從0到1系統(tǒng)精講 項(xiàng)目開發(fā)綜合基礎(chǔ)課高山流水遇知音
C++的類和對象
下栽の地止:https://lexuecode.com/6665.html
C++ 類 & 對象
C++ 在 C 語言的基礎(chǔ)上增加了面向?qū)ο缶幊蹋珻++ 支持面向?qū)ο蟪绦蛟O(shè)計(jì)
類是 C++ 的核心特性,通常被稱為用戶定義的類型
類用于指定對象的形式,它包含了數(shù)據(jù)表示法和用于處理數(shù)據(jù)的方法
類中的數(shù)據(jù)和方法稱為類的成員
函數(shù)在一個類被稱為類的成員
定義一個類,本質(zhì)上是定義一個數(shù)據(jù)類型的藍(lán)圖
這實(shí)際上并沒有定義任何數(shù)據(jù),但它定義了類的名稱意味著什么,也就是說,它定義了類的對象包括了什么,以及可以在這個對象上執(zhí)行哪些操作
類定義是以關(guān)鍵字 class 開頭,后跟類的名稱
類的主體是包含在一對花括號中
類定義后必須跟著一個分號或一個聲明列表
下面的代碼使用關(guān)鍵字 class 定義 Box 數(shù)據(jù)類型
class Box
{
public:
double length; // 盒子的長度
double breadth; // 盒子的寬度
double height; // 盒子的高度
};
關(guān)鍵字 public 確定了類成員的訪問屬性。在類對象作用域內(nèi),公共成員在類的外部是可訪問的。您也可以指定類的成員為 private 或 protected ,這個我們稍后會進(jìn)行講解。
定義 C++ 對象
類提供了對象的藍(lán)圖,所以基本上,對象是根據(jù)類來創(chuàng)建的
聲明類的對象,就像聲明基本類型的變量一樣
下面的語句聲明了類 Box 的兩個對象
Box Box1; // 聲明 Box1,類型為 Box
Box Box2; // 聲明 Box2,類型為 Box
對象 Box1 和 Box2 都有它們各自的數(shù)據(jù)成員
訪問數(shù)據(jù)成員
類的對象的公共數(shù)據(jù)成員可以使用直接成員訪問運(yùn)算符 (.) 來訪問
#include <iostream>
using namespace std;
class Box
{
public:
double length; // 長度
double breadth; // 寬度
double height; // 高度
};
int main( )
{
Box Box1; // 聲明 Box1,類型為 Box
Box Box2; // 聲明 Box2,類型為 Box
double volume = 0.0; // 用于存儲體積
// box 1 詳述
Box1.height = 5.0;
Box1.length = 6.0;
Box1.breadth = 7.0;
// box 2 詳述
Box2.height = 10.0;
Box2.length = 12.0;
Box2.breadth = 13.0;
// box 1 的體積
volume = Box1.height * Box1.length * Box1.breadth;
cout << "Box1 的體積:" << volume <<endl;
// box 2 的體積
volume = Box2.height * Box2.length * Box2.breadth;
cout << "Box2 的體積:" << volume <<endl;
return 0;
}
編譯和運(yùn)行以上范例,輸出結(jié)果如下
Box1 的體積:210
Box2 的體積:1560
C++ 文件操作
1. 文件的概念
對于用戶來說,常用到的文件有兩大類:程序文件和數(shù)據(jù)文件。而根據(jù)文件中數(shù)據(jù)的組織方式,則可以將文件分為ASCII 文件和二進(jìn)制文件。
ASCII 文件,又稱字符文件或者文本文件,它的每一個字節(jié)放一個 ASCII 代碼,代表一個字符。
二進(jìn)制文件,又稱內(nèi)部格式文件或字節(jié)文件,是把內(nèi)存中的數(shù)據(jù)按其在內(nèi)存中的存儲形式原樣輸出到磁盤上存放。
數(shù)字 64 在內(nèi)存中表示為 0100 0000,若將其保存為 ASCII 文件,則要分別存放十位 6 和個位 4 的 ASCII 碼,為 0011 0110 0011 0100,占用兩個字節(jié);若將其保存為二進(jìn)制文件,則按內(nèi)存中形式直接輸出,為 0100 0000,占用一個字節(jié)。
ASCII 文件中數(shù)據(jù)與字符一一對應(yīng),一個字節(jié)代表一個字符,可以直接在屏幕上顯示或打印出來,這種方式使用方便,比較直觀,便于閱讀,但一般占用存儲空間較大,而且輸出時(shí)要將二進(jìn)制轉(zhuǎn)化為 ASCII 碼比較花費(fèi)時(shí)間。
二進(jìn)制文件,輸出時(shí)不需要進(jìn)行轉(zhuǎn)化,直接將內(nèi)存中的形式輸出到文件中,占用存儲空間較小,但一個字節(jié)并不對應(yīng)一個文件,不能直觀顯示文件中的內(nèi)容。
2. 文件流和文件流對象
文件流是以外存文件未輸入輸出對象的數(shù)據(jù)流。輸出文件流是從內(nèi)存流向外存文件的數(shù)據(jù),輸入文件流是從外存文件流向內(nèi)存的數(shù)據(jù)。每一個文件流都有一個內(nèi)存緩沖區(qū)與之對應(yīng)。
C++ 中有三個用于文件操作的文件類:
ifstream 類,它是從 istream 類派生來的,用于支持從磁盤文件的輸入。
ofstream 類,它是從 ostream 類派生來的,用于支持向磁盤文件的輸出。
fstream 類,它是從 iostream 類派生來的,用于支持對磁盤文件的輸入輸出。
要以磁盤文件為對象進(jìn)行輸入輸出,必須定義一個文件流類的對象,通過文件流對象將數(shù)據(jù)從內(nèi)存輸出到磁盤文件,或者將磁盤文件輸入到內(nèi)存。
定義文件流對象后,我們還需要將文件流對象和指定的磁盤文件建立關(guān)聯(lián),以便使文件流流向指定的磁盤文件,并確定文件的工作方式(輸入還是輸出,二進(jìn)制還是 ASCII)。我們可以在定義流對象的時(shí)候指定參數(shù)來調(diào)用構(gòu)造函數(shù),或者通過成員函數(shù) open 來進(jìn)行文件流對象和指定文件的關(guān)聯(lián)。
3. 對 ASCII 文件的操作
然后,我們就可以用類似 cin 或者 cout 的方式將數(shù)據(jù)讀出或?qū)懭胛募?,只不過是輸入輸出的對象變成了文件而已。當(dāng)然,在對磁盤文件完成讀寫操作后,我們可以通過 close 方法來解除磁盤文件和文件流對象的關(guān)聯(lián)。
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ofstream outfile("a.txt", ios::out);
if (!outfile)
{
cerr << "Failed to open the file!";
return 1;
}
// 寫入數(shù)字 1-5 到文件中
for (int i = 1; i < 6; i++)
{
outfile << i << '\n';
}
outfile.close();
ifstream infile("a.txt", ios::in);
if (!infile)
{
cerr << "Failed to open the file!";
return 1;
}
char data; // 從文件中讀出數(shù)字 1-5
for (int i = 1; i < 6; i++)
{
infile >> data;
cout << data << '\n';
}
infile.close();
return 0;
}
也可以利用文件流對象的成員函數(shù) get, put 等,其用法就和標(biāo)準(zhǔn)輸入輸出介紹的一樣。
int main()
{
ofstream outfile("a.txt", ios::out);
if (!outfile)
{
cerr << "Failed to open the file!";
return 1;
}
for (char i = '1'; i < '6'; i++)
{
outfile.put(i); // 輸出一個字符到文件中去
}
outfile.close();
ifstream infile("a.txt", ios::in);
if (!infile)
{
cerr << "Failed to open the file!";
return 1;
}
/*char a;
for (int i = 0; i < 5; i++)
{
infile.get(a); // 從文件中讀出 1 個字符
cout << a << '\n';
}*/
char data[5];
infile.get(data, 6); // 從文件中讀出 5 個字符
for (int i = 0; i < 5; i++)
{
cout << data[i] << '\n';
}
infile.close();
return 0;
}
4. 對二進(jìn)制文件的操作
二進(jìn)制文件的操作需要在打開文件的時(shí)候指定打開方式為 ios::binary,并且還可以指定為既能輸入又能輸出的文件,我們通過成員函數(shù) read 和 write 來讀寫二進(jìn)制文件。
istream& read (char* s, streamsize n);
ostream& write (const char* s, streamsize n);
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ofstream outfile("a.txt", ios::binary);
if (!outfile)
{
cerr << "Failed to open the file!";
return 1;
}
char a[] = {'h', 'e', 'l', 'l', 'o', ','};
char b[] = {'s', 'e', 'n', 'i', 'u', 's', 'e', 'n', '!'};
outfile.write(a, 6); // 將以 a 為首地址的 6 個字符寫入文件
outfile.write(b, 9);
outfile.close();
ifstream infile("a.txt", ios::binary);
if (!infile)
{
cerr << "Failed to open the file!";
return 1;
}
char data[6];
infile.read(data, 6); // 從文件中讀出 6 個字符到以 data 為首地址的字符數(shù)組中
for (int i = 0; i < 6; i++)
{
cout << data[i];
}
char datb[6];
infile.read(datb, 9);
for (int i = 0; i < 9; i++)
{
cout << datb[i];
}
infile.close();
return 0;
}
在磁盤文件中有一個文件指針,用來指明當(dāng)前讀寫的位置。每次寫入或者讀出一個字節(jié),指針就向后移動一個字節(jié)。對于二進(jìn)制文件,允許對指針進(jìn)行控制,使它移動到所需的位置,以便在該位置上進(jìn)行讀寫。
ostream& seekp (streampos pos);將輸出文件中指針移動到指定的位置
ostream& seekp (streamoff off, ios_base::seekdir way);以參照位置為基準(zhǔn)對輸出文件中的指針移動若干字節(jié)
streampos tellp();返回輸出文件指針當(dāng)前的位置
istream& seekg (streampos pos);將輸入文件中指針移動到指定的位置
istream& seekg (streamoff off, ios_base::seekdir way);以參照位置為基準(zhǔn)對輸入文件中的指針移動若干字節(jié)
streampos tellg();返回輸入文件指針當(dāng)前的位置
其中,參照位置有以下幾個選擇:
ios_base::beg文件開始位置
ios_base::cur文件當(dāng)前位置
ios_base::end文件末尾位置
c++網(wǎng)絡(luò)編程基礎(chǔ)
網(wǎng)絡(luò)編程和套接字
網(wǎng)絡(luò)編程其實(shí)和我們計(jì)算機(jī)上的文件讀取操作很類似,通俗地講,網(wǎng)絡(luò)編程就是編寫程序使兩臺聯(lián)網(wǎng)的計(jì)算機(jī)相互交換數(shù)據(jù)。那么,數(shù)據(jù)具體怎么傳輸呢?其實(shí)操作系統(tǒng)會提供名為“套接字”的部件,套接字就是網(wǎng)絡(luò)數(shù)據(jù)傳輸用的軟件設(shè)備而已。即使你對網(wǎng)絡(luò)數(shù)據(jù)傳輸原理不太熟悉,你也可以通過套接字完成數(shù)據(jù)傳輸。因此,網(wǎng)絡(luò)編程常常又稱為套接字編程。
下面我們再通過一個通俗地例子來理解什么是套接字并給出創(chuàng)建它的過程。實(shí)際上,這個過程類似我們的電話機(jī)系統(tǒng),電話機(jī)通過固定電話網(wǎng)完成語言數(shù)據(jù)的交換。這里的電話機(jī)就類似我們的套接字,電網(wǎng)就類似我們的互聯(lián)網(wǎng)。和電話可以撥打或接聽一樣,套接字也可以發(fā)送或接收。先來看看接收的套接字創(chuàng)建過程:
1,打電話首先需要準(zhǔn)備什么?當(dāng)然得是要先有一臺電話機(jī)。創(chuàng)建相當(dāng)于電話機(jī)的套接字,如下:
int socket(int domain, int type, int protocol);
2,準(zhǔn)備好電話機(jī)后要考慮分配電話號碼的問題,這樣別人才能聯(lián)系到你。套接字也一樣,利用下面函數(shù)創(chuàng)建好套接字分配地址信息(IP地址和端口號)。
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
3,做了上面兩步后,接下來就是需要連接電話線并等待來電。一連接電話線,電話機(jī)就轉(zhuǎn)為了可接聽狀態(tài),這時(shí)其他人可以撥打電話請求連接到該機(jī)了。同樣,需要把套接字轉(zhuǎn)化成可接收連接的狀態(tài)。
int listen(int sockfd, int backlog);
4,前面都做好后,如果有人撥打電話就會響鈴,拿起話筒才能接聽電話。套接字同樣如此,如果有人為了完成數(shù)據(jù)傳輸而請求連接,就需要調(diào)用下面函數(shù)進(jìn)行受理。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
總結(jié)下網(wǎng)絡(luò)中接收連接請求的套接字創(chuàng)建過程如下:
第一步:調(diào)用socket函數(shù)創(chuàng)建套接字。
第二步:調(diào)用bind函數(shù)分配IP地址和端口號。
第三部:調(diào)用listen函數(shù)轉(zhuǎn)為可接收請求狀態(tài)。
第四步:調(diào)用accept函數(shù)受理連接請求。
上面講的都是接電話,即服務(wù)端套接字(接收),下面我們再來講講打電話,即客服端套接字(發(fā)送)。這個要簡單,只有兩步:1,調(diào)用socket函數(shù)創(chuàng)建套接字。2,調(diào)用connect函數(shù)向服務(wù)端發(fā)送連接請求。
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
基于Linux的文件操作
1,在這里為什么要討論Linux上的文件操作呢?因?yàn)長inux上,socket操作與文件操作沒有區(qū)別,在Linux上,socket也被認(rèn)為是文件的一種。
注:Linux上的C語言編譯器–GCC,具體使用就不在這里講了。
2,文件描述符:是系統(tǒng)自動分配給文件或套接字的整數(shù)。下面我們再來通過一個例子理解下它:假設(shè)學(xué)校有個打印室,只需要打個電話就能復(fù)印所需論文。有一位同學(xué),經(jīng)常打電話要復(fù)印這樣個內(nèi)容:“<<關(guān)于隨著高度信息化社會而逐漸提升地位的觸覺,知覺,思維,性格,智力等人類生活質(zhì)量相關(guān)問題特性的人類學(xué)研究>>這篇論文第26頁到30頁”。終于有一天,打印室的人感覺這樣太不方便了,于是,打印室的人和那位同學(xué)說:“以后那篇論文就編為第18號,你就說幫我復(fù)印18號論文26頁到30頁”。在該例中,打印室相當(dāng)于操作系統(tǒng),那位同學(xué)相當(dāng)于程序員,論文號相當(dāng)于文件描述符,論文相當(dāng)于文件或套接字。也就是說,每當(dāng)生成文件或套接字,操作系統(tǒng)就會自動返回給我們一個整數(shù)。這個整數(shù)就是文件描述符,即創(chuàng)建的文件或套接字的別名,方便稱呼而已。
注:文件描述符在Windows中又稱為句柄。
3,Linux上的文件或套接字操作:
打開文件:
int open(const char *path, int flag); –> (Linux上對應(yīng)socket(…)函數(shù))
關(guān)閉文件或套接字:
int close(int fd); –>(Windows上對應(yīng)closesocket(SOCKET S)函數(shù))
將數(shù)據(jù)寫入文件或傳遞數(shù)據(jù):
ssize_t write(int fd, const void *buf, size_t nbytes);
讀取文件中數(shù)據(jù)或接收數(shù)據(jù):
ssize_t read(int fd, void *buf, size_t nbytes);
注釋:ssize_t = signed int, size_t = unsigned int,其實(shí)它們都是通過typedef聲明的,為基本數(shù)據(jù)類型取的別名而已。既然已經(jīng)有了基本數(shù)據(jù)類型,那么為什么還需要為它取別名呢?是因?yàn)槟壳捌毡檎J(rèn)為int是32位的,而過去16位操作系統(tǒng)時(shí)代,int是16位的。根據(jù)系統(tǒng)的不同,時(shí)代的變化,基本數(shù)據(jù)類型的表現(xiàn)形式也隨著變化的。如果為基本數(shù)據(jù)類型取了別名,以后要修改,也就只需要修改typedef聲明即可,這將大大減少代碼變動。
基于Windows平臺的實(shí)現(xiàn)
1,Windows套接字大部分是參考BSD系列UNIX套接字設(shè)計(jì)的,所以很多地方都跟Linux套接字類似。因此,只需要更改Linux環(huán)境下編好的一部分網(wǎng)絡(luò)程序內(nèi)容,就能再Windows平臺下運(yùn)行。
2,上面講了Linux上,文件操作和套接字操作一致。但Windows上的I/O函數(shù)和套接字I/O函數(shù)是不同的。
Winsock數(shù)據(jù)傳輸函數(shù):
int send(SOCKET s, const char *buf, int len, int flags);
Winsock數(shù)據(jù)接收函數(shù):
int recv(SOCKET s, const char *buf, int len, int flags);
3,Windows與Linux上的套接字再一個區(qū)別是:Windows上需要先對Winsock庫進(jìn)行初始化,最后退出還要注銷Winsock相關(guān)庫。
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
第一個參數(shù):Winsock中存在多個版本,應(yīng)準(zhǔn)備WORD類型的(WORD是typedef聲明的unsigned short)套接字版本信息。若版本為1.2,則其中1是主版本號,2是副版本號,應(yīng)傳遞0x0201。高8位為副版本號,低8位為主版本號。我們還可以直接使用宏,MAKEWORD(1,2); //主版本號為1,副版本為2,返回0x0201。
第二個參數(shù):就是傳入WSADATA型結(jié)構(gòu)體變量地址。
Winsock庫初始化:
復(fù)制代碼
int main(int argc, char *argv[])
{
WSADATA wsaData;
...
if(WSAStartup(MAKEWORD(1,2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
...
return 0;
}
復(fù)制代碼
在退出時(shí)需要釋放Winsock庫:
int WSACleanup(void); //返回0成功,失敗返回SOCKET_ERROR
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
void main()
{
//加載套接字
WSADATA wsaData;
char buff[1024];
memset(buff, 0, sizeof(buff));
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
printf("Failed to load Winsock");
return;
}
SOCKADDR_IN addrSrv;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(5099);
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//創(chuàng)建套接字
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
if (SOCKET_ERROR == sockClient){
printf("Socket() error:%d", WSAGetLastError());
return;
}
//向服務(wù)器發(fā)出連接請求
if (connect(sockClient, (struct sockaddr*)&addrSrv, sizeof(addrSrv)) == INVALID_SOCKET){
printf("Connect failed:%d", WSAGetLastError());
return;
}
else
{
//接收數(shù)據(jù)
recv(sockClient, buff, sizeof(buff), 0);
printf("%s\n", buff);
}
//發(fā)送數(shù)據(jù)
char *buffSend = "hello, this is a Client....";
send(sockClient, buffSend, strlen(buffSend) + 1, 0);
printf("%d", strlen(buffSend) + 1);
//關(guān)閉套接字
closesocket(sockClient);
WSACleanup();
system("pause");
}