C/C++ 从0到1系统精讲 项目开发综合基础课高山流水遇知音
C++的类和对象
下栽の地止:https://lexuecode.com/6665.html
C++ 类 & 对象
C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计
类是 C++ 的核心特性,通常被称为用户定义的类型
类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法
类中的数据和方法称为类的成员
函数在一个类被称为类的成员
定义一个类,本质上是定义一个数据类型的蓝图
这实际上并没有定义任何数据,但它定义了类的名称意味着什么,也就是说,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作
类定义是以关键字 class 开头,后跟类的名称
类的主体是包含在一对花括号中
类定义后必须跟着一个分号或一个声明列表
下面的代码使用关键字 class 定义 Box 数据类型
class Box
{
public:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
};
关键字 public 确定了类成员的访问属性。在类对象作用域内,公共成员在类的外部是可访问的。您也可以指定类的成员为 private 或 protected ,这个我们稍后会进行讲解。
定义 C++ 对象
类提供了对象的蓝图,所以基本上,对象是根据类来创建的
声明类的对象,就像声明基本类型的变量一样
下面的语句声明了类 Box 的两个对象
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
对象 Box1 和 Box2 都有它们各自的数据成员
访问数据成员
类的对象的公共数据成员可以使用直接成员访问运算符 (.) 来访问
#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;
}
编译和运行以上范例,输出结果如下
Box1 的体积:210
Box2 的体积:1560
C++ 文件操作
1. 文件的概念
对于用户来说,常用到的文件有两大类:程序文件和数据文件。而根据文件中数据的组织方式,则可以将文件分为ASCII 文件和二进制文件。
ASCII 文件,又称字符文件或者文本文件,它的每一个字节放一个 ASCII 代码,代表一个字符。
二进制文件,又称内部格式文件或字节文件,是把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放。
数字 64 在内存中表示为 0100 0000,若将其保存为 ASCII 文件,则要分别存放十位 6 和个位 4 的 ASCII 码,为 0011 0110 0011 0100,占用两个字节;若将其保存为二进制文件,则按内存中形式直接输出,为 0100 0000,占用一个字节。
ASCII 文件中数据与字符一一对应,一个字节代表一个字符,可以直接在屏幕上显示或打印出来,这种方式使用方便,比较直观,便于阅读,但一般占用存储空间较大,而且输出时要将二进制转化为 ASCII 码比较花费时间。
二进制文件,输出时不需要进行转化,直接将内存中的形式输出到文件中,占用存储空间较小,但一个字节并不对应一个文件,不能直观显示文件中的内容。
2. 文件流和文件流对象
文件流是以外存文件未输入输出对象的数据流。输出文件流是从内存流向外存文件的数据,输入文件流是从外存文件流向内存的数据。每一个文件流都有一个内存缓冲区与之对应。
C++ 中有三个用于文件操作的文件类:
ifstream 类,它是从 istream 类派生来的,用于支持从磁盘文件的输入。
ofstream 类,它是从 ostream 类派生来的,用于支持向磁盘文件的输出。
fstream 类,它是从 iostream 类派生来的,用于支持对磁盘文件的输入输出。
要以磁盘文件为对象进行输入输出,必须定义一个文件流类的对象,通过文件流对象将数据从内存输出到磁盘文件,或者将磁盘文件输入到内存。
定义文件流对象后,我们还需要将文件流对象和指定的磁盘文件建立关联,以便使文件流流向指定的磁盘文件,并确定文件的工作方式(输入还是输出,二进制还是 ASCII)。我们可以在定义流对象的时候指定参数来调用构造函数,或者通过成员函数 open 来进行文件流对象和指定文件的关联。
3. 对 ASCII 文件的操作
然后,我们就可以用类似 cin 或者 cout 的方式将数据读出或写入文件,只不过是输入输出的对象变成了文件而已。当然,在对磁盘文件完成读写操作后,我们可以通过 close 方法来解除磁盘文件和文件流对象的关联。
#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;
}
// 写入数字 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; // 从文件中读出数字 1-5
for (int i = 1; i < 6; i++)
{
infile >> data;
cout << data << '\n';
}
infile.close();
return 0;
}
也可以利用文件流对象的成员函数 get, put 等,其用法就和标准输入输出介绍的一样。
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. 对二进制文件的操作
二进制文件的操作需要在打开文件的时候指定打开方式为 ios::binary,并且还可以指定为既能输入又能输出的文件,我们通过成员函数 read 和 write 来读写二进制文件。
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 为首地址的字符数组中
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;
}
在磁盘文件中有一个文件指针,用来指明当前读写的位置。每次写入或者读出一个字节,指针就向后移动一个字节。对于二进制文件,允许对指针进行控制,使它移动到所需的位置,以便在该位置上进行读写。
ostream& seekp (streampos pos);将输出文件中指针移动到指定的位置
ostream& seekp (streamoff off, ios_base::seekdir way);以参照位置为基准对输出文件中的指针移动若干字节
streampos tellp();返回输出文件指针当前的位置
istream& seekg (streampos pos);将输入文件中指针移动到指定的位置
istream& seekg (streamoff off, ios_base::seekdir way);以参照位置为基准对输入文件中的指针移动若干字节
streampos tellg();返回输入文件指针当前的位置
其中,参照位置有以下几个选择:
ios_base::beg文件开始位置
ios_base::cur文件当前位置
ios_base::end文件末尾位置
c++网络编程基础
网络编程和套接字
网络编程其实和我们计算机上的文件读取操作很类似,通俗地讲,网络编程就是编写程序使两台联网的计算机相互交换数据。那么,数据具体怎么传输呢?其实操作系统会提供名为“套接字”的部件,套接字就是网络数据传输用的软件设备而已。即使你对网络数据传输原理不太熟悉,你也可以通过套接字完成数据传输。因此,网络编程常常又称为套接字编程。
下面我们再通过一个通俗地例子来理解什么是套接字并给出创建它的过程。实际上,这个过程类似我们的电话机系统,电话机通过固定电话网完成语言数据的交换。这里的电话机就类似我们的套接字,电网就类似我们的互联网。和电话可以拨打或接听一样,套接字也可以发送或接收。先来看看接收的套接字创建过程:
1,打电话首先需要准备什么?当然得是要先有一台电话机。创建相当于电话机的套接字,如下:
int socket(int domain, int type, int protocol);
2,准备好电话机后要考虑分配电话号码的问题,这样别人才能联系到你。套接字也一样,利用下面函数创建好套接字分配地址信息(IP地址和端口号)。
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
3,做了上面两步后,接下来就是需要连接电话线并等待来电。一连接电话线,电话机就转为了可接听状态,这时其他人可以拨打电话请求连接到该机了。同样,需要把套接字转化成可接收连接的状态。
int listen(int sockfd, int backlog);
4,前面都做好后,如果有人拨打电话就会响铃,拿起话筒才能接听电话。套接字同样如此,如果有人为了完成数据传输而请求连接,就需要调用下面函数进行受理。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
总结下网络中接收连接请求的套接字创建过程如下:
第一步:调用socket函数创建套接字。
第二步:调用bind函数分配IP地址和端口号。
第三部:调用listen函数转为可接收请求状态。
第四步:调用accept函数受理连接请求。
上面讲的都是接电话,即服务端套接字(接收),下面我们再来讲讲打电话,即客服端套接字(发送)。这个要简单,只有两步:1,调用socket函数创建套接字。2,调用connect函数向服务端发送连接请求。
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
基于Linux的文件操作
1,在这里为什么要讨论Linux上的文件操作呢?因为Linux上,socket操作与文件操作没有区别,在Linux上,socket也被认为是文件的一种。
注:Linux上的C语言编译器–GCC,具体使用就不在这里讲了。
2,文件描述符:是系统自动分配给文件或套接字的整数。下面我们再来通过一个例子理解下它:假设学校有个打印室,只需要打个电话就能复印所需论文。有一位同学,经常打电话要复印这样个内容:“<<关于随着高度信息化社会而逐渐提升地位的触觉,知觉,思维,性格,智力等人类生活质量相关问题特性的人类学研究>>这篇论文第26页到30页”。终于有一天,打印室的人感觉这样太不方便了,于是,打印室的人和那位同学说:“以后那篇论文就编为第18号,你就说帮我复印18号论文26页到30页”。在该例中,打印室相当于操作系统,那位同学相当于程序员,论文号相当于文件描述符,论文相当于文件或套接字。也就是说,每当生成文件或套接字,操作系统就会自动返回给我们一个整数。这个整数就是文件描述符,即创建的文件或套接字的别名,方便称呼而已。
注:文件描述符在Windows中又称为句柄。
3,Linux上的文件或套接字操作:
打开文件:
int open(const char *path, int flag); –> (Linux上对应socket(…)函数)
关闭文件或套接字:
int close(int fd); –>(Windows上对应closesocket(SOCKET S)函数)
将数据写入文件或传递数据:
ssize_t write(int fd, const void *buf, size_t nbytes);
读取文件中数据或接收数据:
ssize_t read(int fd, void *buf, size_t nbytes);
注释:ssize_t = signed int, size_t = unsigned int,其实它们都是通过typedef声明的,为基本数据类型取的别名而已。既然已经有了基本数据类型,那么为什么还需要为它取别名呢?是因为目前普遍认为int是32位的,而过去16位操作系统时代,int是16位的。根据系统的不同,时代的变化,基本数据类型的表现形式也随着变化的。如果为基本数据类型取了别名,以后要修改,也就只需要修改typedef声明即可,这将大大减少代码变动。
基于Windows平台的实现
1,Windows套接字大部分是参考BSD系列UNIX套接字设计的,所以很多地方都跟Linux套接字类似。因此,只需要更改Linux环境下编好的一部分网络程序内容,就能再Windows平台下运行。
2,上面讲了Linux上,文件操作和套接字操作一致。但Windows上的I/O函数和套接字I/O函数是不同的。
Winsock数据传输函数:
int send(SOCKET s, const char *buf, int len, int flags);
Winsock数据接收函数:
int recv(SOCKET s, const char *buf, int len, int flags);
3,Windows与Linux上的套接字再一个区别是:Windows上需要先对Winsock库进行初始化,最后退出还要注销Winsock相关库。
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
第一个参数:Winsock中存在多个版本,应准备WORD类型的(WORD是typedef声明的unsigned short)套接字版本信息。若版本为1.2,则其中1是主版本号,2是副版本号,应传递0x0201。高8位为副版本号,低8位为主版本号。我们还可以直接使用宏,MAKEWORD(1,2); //主版本号为1,副版本为2,返回0x0201。
第二个参数:就是传入WSADATA型结构体变量地址。
Winsock库初始化:
复制代码
int main(int argc, char *argv[])
{
WSADATA wsaData;
...
if(WSAStartup(MAKEWORD(1,2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
...
return 0;
}
复制代码
在退出时需要释放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");
//创建套接字
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
if (SOCKET_ERROR == sockClient){
printf("Socket() error:%d", WSAGetLastError());
return;
}
//向服务器发出连接请求
if (connect(sockClient, (struct sockaddr*)&addrSrv, sizeof(addrSrv)) == INVALID_SOCKET){
printf("Connect failed:%d", WSAGetLastError());
return;
}
else
{
//接收数据
recv(sockClient, buff, sizeof(buff), 0);
printf("%s\n", buff);
}
//发送数据
char *buffSend = "hello, this is a Client....";
send(sockClient, buffSend, strlen(buffSend) + 1, 0);
printf("%d", strlen(buffSend) + 1);
//关闭套接字
closesocket(sockClient);
WSACleanup();
system("pause");
}