linux C/C++ 学习记录
makefile
makefile和make的关系
makefile:makefile类似脚本,其中记录了编译C++工程的方法
make:make是命令执行工具,用来执行makefile文件
*打个比方,makefile就行计划书,而make则是执行该计划的人
makefile文件的书写格式
target: prerequisites
command
...
其中target为目标文件, prerequisites为一个或多个依赖文件,command为shell命令;当prerequisites比target新时,command命令则会执行。
举例:目前假设有三个文件a.h a.cpp main.cpp(a.cpp 和 main.cpp 包含有a.h), 则makefile文件可编写为:
learn: main.o a.o
g++ main.o a.o -o learn
main.o: main.cpp a.h
g++ main.cpp -c
a.o: a.cpp a.h
g++ a.cpp -c
clean:
rm main.o a.o
makefile的变量
加入prerequisites中文件很多,并且多次出现,那么编写makefile文件就慢,有没有改进的方法的呢?那就是使用变量
变量的定义: 变量名=文件1 文件2 ... 文件n
变量的使用: $(变量名)
举例:还是上面的例子,但是我们引入了变量,则makefile文件可编写为:
object=main.o a.cpp
learn: $(object)
g++ $(object)
main.o: main.cpp a.h
g++ main.cpp -c
a.o: a.cpp a.h
g++ a.cpp a.h
clean:
rm $(object)
可以看到,makefile中的变量类似于C语言中的宏
makefile的隐晦规则
a.o: a.cpp a1.h a2.h ... an.h
g++ a.cpp -c
可以简化为:
a.o: a1.h a2.h ... an.h
这就是makefile的隐晦规则,使得makefile更为简洁
举例:对于上面的例子,makefile可以编写为:
object = main.o a.o
learn: $(object)
main.o: a.h
a.o: a.h
clean:
rm learn $(object)
makefile中的clean
我们可以从上面的makefile都存在
clean:
rm learn $(object)
它的作用在于清理掉编译链接过程中产生的文件,我们可以通过make clean命令来执行
注意:不要把clean放在文件开头,不然make命令会执行clean的指令
引用
引用1
C语言中的输入输出
C语言中把一切设备看作文件,所以无论是对终端设备的读写还是队磁盘的读写,都是一致的
文件的读写
文件的读写分为三个部分:文件的打开,对文件的操作,文件的关闭
文件的打开
文件打开的函数为:
FILE *fopen( const char * filename, const char * mode );
其中mode为文件格式,文件可分类为输入文件和输出文件(输出文件也可分为写入和追加),文本文件和二进制文件, 详细内容可点击这里
文件的操作
文本文件写操作
(1)int fputs(int c, FILE *fp); 向fp指向文件中写入字符,写入成功则返回写入字符,写入失败则返回EOF;
char c = '@';
fputc(c, fp);
(2)int fputs(char *s, FILE *fp); 向fp指向文件中写入字符串,写入成功返回非负值,写入失败返回EOF
fputs("I like sports!", fp);
(3)int fprintf(FILE *fp, char *format, ...); 向fp中写入格式化字符串
fprintf(fp, "今天的气温为%d度-%d度", 19, 28);
文本文件读操作
(1)int fgetc(FILE * fp) 从fp指向的文件中读取一个字符并返回改字符,如果出错,则返回EOF(-1)
char c = fgetc(fp);
(2)char * fgets(char * buf, int n, FILE * fp) 从fp指向的文件中读取n-1个字符 + \0 置于字符数组buf中,如果读取过程中提前遇到EOF或者\n,则会提前结束读取(\n也会读取); 正常情况下返回读取的字符串的首地址, 异常情况下返回NULL
char str[100];
char * str2;
str2 = fgets(str, 20, fp);
printf("string=%s", str);
printf("string2=%s", str2);
(3)int fscanf(FILE * fp, const char * format, ...) 从fp指向的文件中读取格式化字符串;如果正确读取,则返回变量的总个数,如果出现错误,则返回出错的索引号。
int a;
float b;
char c;
char s[10];
fscanf(fp, "%d%f%s%c", &a, &b, s, &c);
printf("a=%d\nb=%f\ns=%s\nc=%c\n", a, b, s, c);
二进制文件写操作
size_t fwrite(const void *ptr, size_t size_of_elements,
size_t number_of_elements, FILE *a_file);
ptr: 存储区的首地址
size_of_elements: 数据项类型大小
number_of_elements: 数据项的数目
file: 写入的二进制文件
fp = fopen("ofile", "wb");
struct Word{
char token[10];
char pos[10];
}word[2];
strcpy(word[0].token, "Mark");
strcpy(word[0].pos, "N");
strcpy(word[1].token, "like");
strcpy(word[1].pos, "V");
fwrite(word, sizeof(struct Word), 2, fp);
fclose(fp);
二进制文件读操作
size_t fread(const void *ptr, size_t size_of_elements, size_t number_of_elements, FILE *a_file);
fp = fopen("ofile", "rb");
fread(word, sizeof(struct Word), 2, fp);
for(int i=0; i<2; ++i){
printf("[%s, %s]\n", word[i].token, word[i].pos);
}
fclose(fp);
文件的关闭
int fclose(FILE * fp);
标准输入输出
因为终端是用户最常用的输入输出,如果每次对终端的读写都需要用户自己去定义文件的打开和关闭,就会很不方便。于是,人们就把键盘文件,屏幕文件单独现在stdio.h中定义好,分别为stdin,stdout,再创建与文件读写操作相似的函数(去掉f,比如fprintf变成printf),来方便用户的使用。由于和上面的基本一直,就不再赘述。
引用
引用1
引用2
枚举类型的输出
C/C++中枚举类型本质上是一个int类型的数字,类似与宏。所以输出枚举类型的变量时得到的是int类型的数字。
#include <stdio.h>
typedef enum{
RED, BULE, YELLOW
}Color;
int main(){
Color c = RED;
printf("Color c = %d (本质int)", c);
}
C语言动态内存的分配和释放
分配: void * malloc(size_t size);
分配size个字节的内存空间, 并返回内存空间的首地址
释放: void free(void *p)
释放p指向的动态内存空间
举例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Word{
char token[10];
char pos[10];
};
int main(){
//动态分配对象并释放
Word * w = (Word *)malloc(sizeof(struct Word));
free(w);
//动态分配数组并释放
strcpy(w->token, "Mark");
strcpy(w->pos, "nr");
printf("word=(%s, %s)\n", w->token, w->pos);
free(w);
//动态分配数组
int *p = (int *)malloc(sizeof(int)*5);
for(int i=0; i<5; ++i){
p[i] = i;
}
for(int i=0; i<5; ++i){
printf("%d\n", p[i]);
}
free(p);
}
疑问:释放内存空间是必要的吗,操作系统会帮我们完成吗?
答:好的操作系统会在main结束后自动释放动态内存。释放内存空间是否必要看具体环境,如果你在程序中动态分配的内存占总内存比例很大,那么不及时释放内存可能会减慢系统运行速度甚至崩溃,反之可以不用手动释放。
C++中的文件读写
C++的文件读写思想和C一致,即一切设备看成文件。不同是C++采用面向对象的方法,可以说istream/ifstream对象是封装了scanf/fscanf方法的对象,ostream/ofstream对象是封装了printf/fprintf的对象。
其中文件的读写包括3个步骤,文件打开,文件操作,文件关闭
文本文件的读写
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main(){
string str;
ifstream in("input", ios::in);
if(!in){
cerr << "打开input文件失败" << endl;
}
ofstream out("output", ios::out);
if(!out){
cerr << "打开output文件失败" << endl;
}
in >> str;
out << str;
in.close();
out.close();
}
标准输入输出就是对文本文件输入输出的特殊形式,省略了程序员自己定义文件流和关闭文件流的时间,这和C中专门设置标准文件的输入输出函数的思想是一致的。
二进制文件的读写
C++动态内存的分配和释放
#include <iostream>
using namespace std;
int main(){
//动态对象的分配和释放
int * p = new int{5};
cout << *p << endl;
delete p;
//动态数组的分配和释放
p = new int[4]{1, 2, 3, 4};
for(int i=0; i<4; ++i)
cout << p[i] << " ";
}
C++类
下面通过例子来讲解
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
//类声明部分
class Box{
private:
//普通属性
double length; //长
double breadth; //宽
double height; //高
//静态属性
static string name; //名字
public:
//一般构造函数
Box(double l = 1, double b = 1, double h = 1);
//复制构造函数
Box(const Box &box);
//析构函数
~Box();
//静态成员函数
static string getName();
//普通成员函数
double getVolume();
};
//类定义部分
string Box::name = "Box";
Box::Box(double l, double b, double h):
length(l), breadth(b), height(h){
cout << "Object is being created" << endl;
}
Box::Box(const Box &box){
length = box.length;
breadth = box.breadth;
height = box.height;
}
Box::~Box(){
cout << "Object is being deleted" << endl;
}
string Box::getName(){
return name;
}
double Box::getVolume(void){
return length * breadth * height;
}
int main(){
//Box类
Box box(1, 2, 3);
cout << box.getName() << endl;
cout << Box::getName() << endl;
cout << box.getVolume() << endl;
Box *bp = new Box(box);
cout << box.getVolume() << endl;
}
C++类的一般组成部分
属性
double length;
double breadth;
double height;
这是普通属性,即每个对象都拥有自己的一套属性,就像一个班的人都拥有属于自己的名字和学号
static string name;
这是静态属性,即该类的所有对象共同拥有,就像一个班的人共同使用唯一一个篮球
成员函数
//声明部分
double getVolume();
//定义部分
double Box::getVolume(void){
return length * breadth * height;
}
这是普通成员函数,有this指针,能访问对象的普通成员
//声明部分
static string getName();
//定义部分
string Box::getName(){
return name;
}
这是静态成员函数,由于没有this指针, 只能访问类的静态属性
构造函数和析构函数
//声明部分
Box(double l=1, double b=1, double h=1);
//定义部分
Box::Box(double l, double b, double h):
length(l), breadth(b), height(h){
cout << "Object is being created" << endl;
}
这是一般的构造函数, 负责普通属性的初始化,会在类对象创建时隐式调用
//声明部分
Box(const Box &box);
//类定义部分
Box::Box(const Box &box){
length = box.length;
breadth = box.breadth;
height = box.height;
}
这是复制构造函数,使得创建出和被复制对象一模一样的新对象
//声明部分
~Box();
//类定义部分
Box::~Box(){
cout << "Object is being deleted" << endl;
}
这是析构函数,会在对象结束生命周期前隐式调用
C++类的访问修饰符
成员的访问修饰符有三种:public protected private
C++类的有元(有元函数/有元类)
C++类的声明和定义(函数和静态属性)
我们可以看到上面的函数或静态属性,都采用了先声明后定义的形式,例如
//Box.h(声明)
class Box{
static string name;
double getVolume();
}
//Box.cpp(定义)
string Box:box name = "Box";
double Box::getVolume(){
return length * breath * height;
}
我们往往会把声明部分放在头文件,定义部分访问源文件,这样的好处是使得类变得条例分明,便于阅读。
注意: 对与静态属性来说,一定需要声明后定义; 而对于函数来说,可以不需要声明,把类的定义写入class{}中即可。
C++类的创建
//第一种
Box box(1, 2, 3);
//第二种
Box * box = new Box(1, 2, 3);
C++类的使用
对于普通成员:采用对象.成员的形式,例如
box.getName();
对于静态成员:既可以采用对象名.成员的形式,也可以采用类::成员的形式,例如
box.getName();
Box::getName();
C++类对象的返回
C++ 隐式构造函数和显式构造函数
先举个例子
#include <iostream>
using namespace std;
class A{
public:
int data;
A(int d){data = d;}
};
int main(){
A a = 1;
cout << "a.data = " << a.data << endl;
}
大家觉得上面的程序有问题吗?有人可能会说 A a = 1 这部分有问题,int类型怎么可以赋值给A对象呢?
但是,实际上面程序是可以正常运行的,因为类中的构造函数默认是隐式的,编译器通过构造函数会默认把int类型转换为A类型。这有时候很方便,有时候也可能出现一些问题(如果你不希望这种行为发生的话)。
要阻止这种隐式转换,我们在构造函数前面添加explicit即可
#include <iostream>
using namespace std;
class A{
public:
int data;
explicit A(int d){data = d;}
};
int main(){
A a = 1;
cout << "a.data = " << a.data << endl;
}
再次运行就会报错
C++类型转换
引用
引用1
C++之QT
最基本的QT程序
下面介绍就简单的QT程序,别看他简单,它可以作为其他一起QT程序的基本模板
#include <QApplication>
#include <QtWidgets>
#include <QLabel>
int main(int argc, char *argv[]){
//应用程序抽象类
QApplication app(argc, argv);
//窗口
QWidget w;
//显示
w.show();
//进入消息循环,一直接受消息,直到窗口被关闭
return app.exec();
}
QT的Hello World程序
下面我们来介绍更复杂一些的QT程序,即给窗口添加一些内容。
#include <QApplication>
#include <QtWidgets>
#include <QLabel>
int main(int argc, char *argv[]){
//应用程序抽象类
QApplication app(argc, argv);
//窗口
QWidget w;
//标签对象
QLabel textLabel;
textLabel.resize(100, 15);//设置宽高
textLabel.move(150, 170);//设置坐标位置(窗口左上角为坐标原点)
textLabel.setText("Hello QT!"); //设置内容
textLabel.setParent(&w);//设置父窗口
//设置窗口标题
w.setWindowTitle("Hello QT!");
//显示窗口
w.show();
//进入消息循环,一直接受消息,直到窗口被关闭
return app.exec();
}
QT中QWidget的子类
上面的程序把窗口中的内容写在main函数中,相较于最基本的QT程序而言,复杂了一些,我们可以通过定义QWidget的子类,是的部件的定义封装起来,让main函数看起来简洁。
mywidget.h
#ifndef _MYWIDGET_H_
#define _MYWIDGET_H_
#include <QWidget>
#include <QLabel>
class Mywidget : public QWidget{
Q_OBJECT
public:
explicit Mywidget(QWidget *parent = nullptr);
QLabel* textLabel;
signals: //用来声明信号函数
public slots: //用来声明槽函数
};
#endif
mywidget.cpp
#include "mywidget.h"
Mywidget::Mywidget(QWidget * parent) : QWidget(parent){
//我们往往在构造函数完成对窗口的初始化工作
//设置窗口大小
resize(400, 400);
//设置窗口标题
setWindowTitle("Hello QT!");
//初始化textLabel:文字为“Hello QT!", 父对象为Mywidget窗口对象
textLabel = new QLabel("Hello QT!", this);
//设置textLabel的位置和宽高
textLabel->setGeometry(150, 100, 130, 50);
}
main.cpp
#include <QApplication>
#include "mywidget.h"
int main(int argc, char *argv[]){
//应用程序抽象类
QApplication app(argc, argv);
//窗口
Mywidget w;
//显示
w.show();
//进入消息循环,一直接受消息,直到窗口被关闭
return app.exec();
}
我们可以看到,效果和第二个程序相同,但是main函数如同第一个函数版简洁。
QT之布局
**使用reset函数和setGeometry函数
x为水平方向 y为数值方向
看下面的例子
Mywidget::Mywidget(QWidget * parent) : QWidget(parent){
//我们往往在构造函数完成对窗口的初始化工作
//设置窗口大小
resize(400, 400);
//设置窗口标题
setWindowTitle("Hello QT!");
//初始化textLabel:文字为“Hello QT!", 父对象为Mywidget窗口对象
textLabel = new QLabel("Hello QT!", this);
textLabel->setGeometry(150, 100, 130, 50);
//初始化button
button = new QPushButton("change", this);
button->setGeometry(150, 175, 130, 50);
}
reset(int x, int y)定义窗口的宽高
setGeometry(int x, int y, int x1, int y1)定义部件的左上角位于窗口的(x, y)坐标上,同时宽高为(x1, y1)
布局函数
水平布局
竖直布局
网格布局
表格布局
分组布局
QT之信号和槽
信号函数:信号函数用来发出信号,一般不需要用户定义,比如QPushButton对象就自带clicked信号函数
槽函数:槽函数用于接受信号并执行相应的行为, 需要用户自己定义
连接函数:函数原型为
Qbject::connect(Object * src, SIGNAL(src的信号函数), Object * obj, SLOT(obj的槽函数));
可以看到它是一个静态成员函数, 用来连接src对象的信号函数和obj对象的槽函数,可以使得当src发出信号时,obj接受并执行相应的操作。
下面举个例子
mywidget.h
#ifndef _MYWIDGET_H_
#define _MYWIDGET_H_
#include <QWidget>
#include <QLabel>
#include <QPushButton>
class Mywidget : public QWidget{
Q_OBJECT
private:
QPushButton * button;
QLabel * label;
public:
explicit Mywidget(QWidget *parent = nullptr);
signals: //用来声明信号函数
public slots: //用来声明槽函数
void changeText();
};
#endif
mywidget.cpp
#include "mywidget.h"
Mywidget::Mywidget(QWidget * parent) : QWidget(parent){
//我们往往在构造函数完成对窗口的初始化工作
//设置窗口大小
resize(400, 400);
//初始化标签
label = new QLabel("Hello Qt!", this);
label->setGeometry(150, 150, 100, 50);
//初始化按钮
button = new QPushButton("change", this);
button->setGeometry(150, 250, 100, 50);
//信号槽连接
connect(button, SIGNAL(clicked()), this, SLOT(changeText()));
}
void Mywidget::changeText(){
label->setText("Goodbye Qt!");
}
main.cpp
#include <QApplication>
#include "mywidget.h"
int main(int argc, char *argv[]){
//应用程序抽象类
QApplication app(argc, argv);
//窗口
Mywidget w;
//显示
w.show();
//进入消息循环,一直接受消息,直到窗口被关闭
return app.exec();
}
这里把按钮的信号函数clicked()和窗口的槽函数changeText()连接起来,使得点击按钮后,标签的文本会发生改变
点击前:
点击后:
Qt之QMainWindow
我们一般使用的软件都带有菜单栏,工具栏,停靠窗口等内容,比如vscode,而QWidget对象中显然不具有这些。所以我们来学习一中新的窗口类QMainWindow
简单的QmainWindow
#include <QApplication>
#include <QMainWindow>
#include <QMenuBar>
#include <QToolBar>
int main(int argc, char *argv[]){
//应用程序抽象类
QApplication app(argc, argv);
//窗口
QMainWindow w;
//添加菜单
w.menuBar()->addMenu(QObject::tr("&File"));
//添加工具
w.addToolBar(QObject::tr("$File"));
//显示
w.show();
//进入消息循环,一直接受消息,直到窗口被关闭
return app.exec();
}
上面的窗口被分为几个部分:
复杂的QMainWindow
mywindow.h
#ifndef _MYWINDOW_H_
#define _MYWINDOW_H_
#include <QMainWindow>
#include <QPlainTextEdit>
class Mywindow : public QMainWindow{
Q_OBJECT
private:
QPlainTextEdit * edit1, * edit2;
QAction * openAction;
QAction * runAction;
public:
explicit Mywindow(QWidget *parent = nullptr);
signals: //用来声明信号函数
public slots: //用来声明槽函数
void open();
void run();
};
#endif
mywindow.cpp
#include "mywindow.h"
#include <QAction>
#include <QMenuBar>
#include <QToolBar>
#include <QHBoxLayout>
#include <iostream>
using namespace std;
Mywindow::Mywindow(QWidget * parent) : QMainWindow(parent){
setWindowTitle(tr("Main Window"));
//文本编辑框
edit1 = new QPlainTextEdit(this);
edit2 = new QPlainTextEdit(this);
QWidget * cw = new QWidget();
QHBoxLayout * layout = new QHBoxLayout(cw);
layout->addWidget(edit1);
layout->addWidget(edit2);
setCentralWidget(cw);
//动作
openAction = new QAction(QIcon(":/images/open"), tr("&Open..."), this);
openAction->setStatusTip(tr("Open a source file"));
runAction = new QAction(QIcon(":/images/run"), tr("&Run..."), this);
runAction->setStatusTip(tr("Run the program"));
connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
connect(runAction, SIGNAL(triggered()), this, SLOT(run()));
//工具栏
QToolBar * openToolBar = addToolBar(tr("&Open"));
openToolBar->addAction(openAction);
QToolBar * runToolBar = addToolBar(tr("&Run"));
runToolBar->addAction(runAction);
//菜单栏
//状态栏
statusBar();
}
void Mywindow::open(){
cout << "open发生" << endl;
}
void Mywindow::run(){
cout << "run发生" << endl;
}
main.cpp
#include "mywindow.h"
#include <QApplication>
int main(int argc, char *argv[]){
//应用程序抽象类
QApplication app(argc, argv);
//窗口
Mywindow w;
w.show();
//进入消息循环,一直接受消息,直到窗口被关闭
return app.exec();
}
res.qrc
<RCC>
<qresource prefix="/images">
<file alias="open">open.jpg</file>
</qresource>
<qresource prefix="/images/">
<file alias="run">run.jpg</file>
</qresource>
</RCC>
下面进行解释:
mywidget.h: 这里进行了自定义类的声明,它继承与QMainWindow类,继承该类的原因是我们可以使用该类所拥有的菜单栏,状态栏,工具栏等
mywidget.cpp: 这里是自定义类的定义
(1)首先我们定义了两个文本编辑框并将它们水平布局,最后将布局加入主窗口的CentralWidget上;
(2)然后我们创建两个QAction对象, QAction顾名思义是动作,可以作为信号发出源,然而动作并没有具体的实体,需要通过addAction(QAction * aciton)添加到具体的实体比如菜单项,工具等等。它的好处是一个动作可以被多个实体所包含,使其有一样的功能。
main.cpp: 相信大家都很熟悉
res.qrc: 这是一个资源文件,其中的open.jpg和run.jpg是用户自己加入工作目录中的文件, /images/open和/images/run是为open.jpg和run.jpg起得别名,我们可以在程序中通过":/images/open"和":/images/run"使用这些图片。
引用
引用
C++之string类
对象的构造
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1 = "string1"; //构造方法1
string s2("string2"); //构造方法2
cout << "s1 = " << s1 << endl;
cout << "s2 = " << s2 << endl;
}
运行结果:
s1 = string1
s2 = string2
find函数
find函数可以求字符或者子串的开始字符
#include <iostream>
using namespace std;
int main(){
string str = "S' -> S\nS -> a";
cout << "换行符所在位置为:" << str.find('\n') << endl;
}
运行结果:
换行符所在位置为:7
substr函数
substr函数可以来求字符串的字串,其基本用法为
//求从from位置开始,大小为len的子串
substr(from, len)
//求从from下标开始,到字符串结束的子串
substr(from)
举个例子:
#include <iostream>
using namespace std;
int main(){
string str = "abcdefg";
cout << "求从下标3开始,长度为2的子串: " << str.substr(3, 2) << endl;
cout << "求从下标4开始到末尾的子串: " << str.substr(4) << endl;
}
运行结果:
求从下标3开始,长度为2的子串: de
求从下标4开始到末尾的子串: efg
SLT容器
vector
Why:为什么要vector?
一维数组的一个特点就是定长。在非定长的存储任务中,如果开辟非常大的空间上限,容易造成空间的浪费; 如果开辟比较小的空间上限,容易出现数组溢出的问题。所以我们需要可变数组vector。
What:vector对象是一个可变数组。下面与一维数组进行对比:
数据结构 | 元素类型 | 指针 | 下标 |
---|---|---|---|
一维数组 | 类型 数组名[数组长度] | 普通的指针,比如数组名就是头指针 | 下标为int数字 |
vector对象 | Vector<类型> 对象名 | 迭代器(高级指针),和普通指针几乎有着一样的用法 | 下标为int数字 |
How:vector数据结构的实现可以基于数组也可以基于链表。下面来介绍具体的用法。 |
vector对象的创建
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main(){
//创建元素类型为string的空vector对象v1
vector<string> v1;
//创建元素类型为string,有5个元素(元素值都为”aa")的vector对象v2
vector<string> v2(5, "aa");
}
vector对象的迭代器(指针)
迭代器可以理解为指针,它指向vector对象中某一个元素
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main(){
//创建vector对象
vector<string> v(5, "aa");
//定义迭代器
vector<string>::iterator it;
//获取头迭代器,该迭代器指向第一个元素: begin函数返回头迭代器
it = v.begin();
//获取尾迭代器,该迭代器指向结束,最后元素的下一个位置: end函数返回尾迭代器
it = v.end();
//迭代器的运算
--it; //it指向的原来指向的前一个元素
}
vector对象中元素的访问
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main(){
vector<string> v(5, "aa");
//通过下标访问
cout << "vector对象中第2个元素为: " << v[2] << endl;
//通过迭代器访问
cout << "vector对象中头迭代器指向的元素为: " << *v.begin() << endl;
}
vector对象的遍历
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main(){
vector<string> v(5, "aa");
//方式1:通过下标的方式
for(int i=0; i<v.size(); ++i){
cout << v[0] << " ";
}
cout << endl;
//方式2:通过迭代器的方式
for(vector<string>::iterator it=v.begin(); it!=v.end(); ++it){
cout << *it << " ";
}
cout << endl;
}
增加元素
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main(){
vector<string> v(5, "aa");
cout << "原来的vector对象: ";
for(vector<string>::iterator it=v.begin(); it!=v.end(); ++it){
cout << *it << " ";
}
cout << endl;
//push_back(元素):在vector对象最后插入元素
cout << "正在使用push.back(\"bb\")方法在vector对象最后插入元素..." << endl;
v.push_back("bb");
cout << "插入后的vector对象: ";
for(vector<string>::iterator it=v.begin(); it!=v.end(); ++it){
cout << *it << " ";
}
cout << endl;
//insert(迭代器, 元素):在迭代器指向的元素前插入元素
cout << "正在使用insert(v.begin(), \"cc\")方法在vector对象第一个元素前插入元素..." << endl;
v.insert(v.begin(), "cc");
cout << "插入后的vector对象: ";
for(vector<string>::iterator it=v.begin(); it!=v.end(); ++it){
cout << *it << " ";
}
cout << endl;
}
运行结果:
原来的vector对象: aa aa aa aa aa
正在使用push.back("bb")方法在vector对象最后插入元素...
插入后的vector对象: aa aa aa aa aa bb
正在使用insert(v.begin(), "cc")方法在vector对象第一个元素前插入元素...
插入后的vector对象: cc aa aa aa aa aa bb
删除元素
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main(){
vector<string> v(5, "aa");
cout << "原来的vector对象: ";
for(vector<string>::iterator it=v.begin(); it!=v.end(); ++it){
cout << *it << " ";
}
cout << endl;
//pop_back():删除最后的元素
cout << "正在使用pop_back()方法删除vector对象的最后一个元素..." << endl;
v.pop_back();
cout << "删除后的vector对象: ";
for(vector<string>::iterator it=v.begin(); it!=v.end(); ++it){
cout << *it << " ";
}
cout << endl;
//erase(迭代器):删除迭代器指向的元素
cout << "正在使用erase(v.begin()+1)方法删除第2个元素..." << endl;
cout << "删除后的vector对象: ";
v.erase(v.begin()+1);
for(vector<string>::iterator it=v.begin(); it!=v.end(); ++it){
cout << *it << " ";
}
cout << endl;
}
运行结果:
原来的vector对象: aa aa aa aa aa
正在使用pop_back()方法删除vector对象的最后一个元素...
删除后的vector对象: aa aa aa aa
正在使用erase(v.begin()+1)方法删除第2个元素...
删除后的vector对象: aa aa aa
map
Why:我们会遇到这样一种存储场景,要求存储一组键值对, 比如\('a':1 'b':2...\)。有人会说,那么先构造这样一个结构体struct D{char a, b;}
,再用该结构体数组来存储不就可以了吗?但是这样有一个问题,比如我们想找到'a'所对应的值是什么,就需要遍历整个数组,当数据量庞大的时候,时间消耗很大。还有另一个问题,我们知道一个key只能唯一确定一个value,但是我们很可能存储多个含有相同key的键值对在数组中。所以,为了解决上面的问题,我们可以使用map类。
What:map对象是字典,它存储着键值对(key, value),我们可以根据key快速查找到对应的value。
How:对于map类如何实现我还不清楚。下面来介绍map的具体用法
map的创建
# include <iostream>
# include <map>
using namespace std;
int main(){
map<char, int> dict{{'a',1}, {'b',2}, {'c',3}};
cout << "key: " << "value" << endl;
for(auto& x: dict){
cout << x.first << ": " << x.second << endl;
}
}
运行结果:
key: value
a: 1
b: 2
c: 3
map的迭代器
map<char, int>::iterator it;
it = dict.begin(); //返回第一个元素的迭代器
it = dict.end(); //返回容器结束的迭代器
it = dict.find('c'); //返回指向key所在元素的迭代器
map的元素
map元素为pair对象,该对象的first就是key,second就是value
# include <iostream>
# include <map>
# include <typeinfo>
using namespace std;
int main(){
map<char, int> dict{{'a',1}, {'b',2}, {'c',3}};
cout << "map元素的类型为(std::pair): " << typeid(dict.begin()).name() << endl;
cout << "第一个元素为pair(" << dict.begin()->first << ", " << dict.begin()->second << ")" << endl;
}
运行结果:
map元素的类型为(std::pair): St17_Rb_tree_iteratorISt4pairIKciEE
第一个元素为pair(a, 1)
补充:map元素的排列有一个特点——会按照key值从小到大排列
map访问key对应的value
# include <iostream>
# include <map>
using namespace std;
int main(){
map<char, int> dict{{'a',1}, {'b',2}, {'c',3}};
//通过operator[]
cout << "key:" << 'a' << "对应的value:" << dict['a'] << endl;
//通过at函数
cout << "key:" << 'b' << "对应的value:" << dict.at('b') << endl;
}
运行结果:
key:a对应的value:1
key:b对应的value:2
插入
(1)insert插入
# include <iostream>
# include <map>
using namespace std;
int main(){
map<char, int> dict{{'a',1}, {'b',2}, {'c',3}};
cout << "插入前dict=" << endl;
for(auto& x: dict){
cout << x.first << ": " << x.second << endl;
}
cout << "正在插入pair<char, int>('d', 4)..." << endl;
dict.insert(pair<char, int>('d', 4));
cout << "插入后dict=" << endl;
for(auto& x: dict){
cout << x.first << ": " << x.second << endl;
}
}
运行结果:
插入前dict=
a: 1
b: 2
c: 3
正在插入pair<char, int>('d', 4)...
插入后dict=
a: 1
b: 2
c: 3
d: 4
(2)operator[]插入
# include <iostream>
# include <map>
using namespace std;
int main(){
map<char, int> dict{{'a',1}, {'b',2}, {'c',3}};
cout << "插入前dict=" << endl;
for(auto& x: dict){
cout << x.first << ": " << x.second << endl;
}
cout << "正在插入pair<char, int>('d', 4)..." << endl;
dict['d'] = 4;
cout << "插入后dict=" << endl;
for(auto& x: dict){
cout << x.first << ": " << x.second << endl;
}
}
运行结果:
插入前dict=
a: 1
b: 2
c: 3
正在插入pair<char, int>('d', 4)...
插入后dict=
a: 1
b: 2
c: 3
d: 4
访问
(1)operator[]
#include <iostream>
#include <string>
#include <map>
int main(){
map<int, string> m{{1, "a"}, {2, "b"}};
//访问成功返回key对应的值
cout << m[1] << endl;
//访问失败返回value类型的空对象
cout << (m[3]=="") << endl;
}
运行结果:
a
1
可以看到访问不存在的键值对时会返回空对象
(2)at
#include <iostream>
#include <string>
#include <map>
int main(){
map<int, string> m{{1, "a"}, {2, "b"}};
//访问成功返回key对应的值
cout << m.at(1) << endl;
//访问失败抛出异常
m.at(3);
}
运行结果:
a
terminate called after throwing an instance of 'std::out_of_range'
what(): map::at
可以看到访问不存在的键值对会抛出异常
删除
# include <iostream>
# include <map>
using namespace std;
int main(){
map<char, int> dict{{'a',1}, {'b',2}, {'c',3}};
cout << "删除前dict=" << endl;
for(auto& x: dict){
cout << x.first << ": " << x.second << endl;
}
cout << "正在删除'b'所对应的键值对..." << endl;
dict.erase('b');
cout << "删除后dict=" << endl;
for(auto& x: dict){
cout << x.first << ": " << x.second << endl;
}
}
运行结果:
删除前dict=
a: 1
b: 2
c: 3
正在删除'b'所对应的键值对...
删除后dict=
a: 1
c: 3
查询
查询map有无key关键字有两种方法
(1)find:查询成功返回对应的迭代器,查询失败返回end迭代器
#include <iostream>
#include <map>
int main(){
map<int, char> m{{1, 'a'}, {2, 'b'}};
cout << (m.find(1) != m.end()) << endl;
cout << (m.find(3) == m.end()) << endl;
}
运行结果:
1
1
(2)count:返回key的数量,当然因为key的唯一性,所以只可能返回0或1
#include <iostream>
#include <map>
int main(){
map<int, char> m{{1, 'a'}, {2, 'b'}};
cout << m.count(1) << endl;
cout << m.count(3) << endl;
}
运行结果:
1
0
set
创建
#include <iostream>
#include <set>
int main(){
set<int> s{1, 2, 3};
cout << "集合=";
for(auto &x: s){
cout << x << " ";
}
}
运行结果:
集合=1 2 3
插入
#include <iostream>
#include <set>
int main(){
set<int> s{1, 2, 3};
cout << "插入前集合=";
for(auto &x: s){
cout << x << " ";
}
cout << endl;
cout << "正在调用insert(4)函数插入元素4..." << endl;
s.insert(4);
cout << "插入后集合=";
for(auto &x: s){
cout << x << " ";
}
cout << endl;
}
运行结果:
插入前集合=1 2 3
正在调用insert(4)函数插入元素4...
插入后集合=1 2 3 4
删除
#include <iostream>
#include <set>
int main(){
set<int> s{1, 2, 3};
cout << "删除前集合=";
for(auto &x: s){
cout << x << " ";
}
cout << endl;
cout << "正在调用erase(3)函数删除元素3..." << endl;
s.erase(3);
cout << "删除后集合=";
for(auto &x: s){
cout << x << " ";
}
cout << endl;
}
运行结果:
删除前集合=1 2 3
正在调用erase(3)函数删除元素3...
删除后集合=1 2
查找
#include <iostream>
#include <set>
int main(){
set<int> s{1, 2, 3};
cout << "对于集合s=";
for(auto &x: s){
cout << x << " ";
}
cout << endl;
if(s.find(3) == s.end()){
cout << "3查找失败" << endl;
}
else{
cout << "3查找成功" << endl;
}
if(s.find(4) == s.end()){
cout << "4查找失败" << endl;
}
else{
cout << "4查找成功" << endl;
}
}
运行结果:
对于集合s=1 2 3
3查找成功
4查找失败
集合运算
#include <iostream>
#include <set>
int main(){
set<int> s1{1, 2};
set<int> s2{2, 3};
set<int> result;
//交运算
set_intersection(s1.begin(), s1.end(), s2.begin(), s2.end(), inserter(result, result.begin()));
cout << "s1 交 s2 = ";
for(auto &x: result){
cout << x << " ";
}
cout << endl;
//并运算
result.clear();
set_union(s1.begin(), s1.end(), s2.begin(), s2.end(), inserter(result, result.begin()));
cout << "s1 并 s2 = ";
for(auto &x: result){
cout << x << " ";
}
cout << endl;
//差运算
result.clear();
set_difference(s1.begin(), s1.end(), s2.begin(), s2.end(), inserter(result, result.begin()));
cout << "s1 差 s2 = ";
for(auto &x: result){
cout << x << " ";
}
cout << endl;
}
s1 交 s2 = 2
s1 并 s2 = 1 2 3
s1 差 s2 = 1
queue
创建
queue对象初始化的方法比较奇怪,不能使用初始化列表,但是可以用其它容器来初始化
#include <iostream>
#include <queue>
int main(){
deque<int> values{1, 2, 3};
queue<int> q(values);
cout << "队列=";
while(!q.empty()){
cout << q.front() << " ";
q.pop();
}
}
运行结果:
队列=1 2 3
插入
队列只能插入队尾
#include <iostream>
#include <queue>
int main(){
//创建并初始化
deque<int> values{1, 2, 3};
queue<int> q(values);
//插入
q.push(4);
//展示
cout << "插入后队列=";
while(!q.empty()){
cout << q.front() << " ";
q.pop();
}
}
运行结果:
插入后队列=1 2 3 4
删除
队列只能删除队首元素
#include <iostream>
#include <queue>
int main(){
//创建并初始化
deque<int> values{1, 2, 3};
queue<int> q(values);
//插入
q.pop();
//展示
cout << "删除后队列=";
while(!q.empty()){
cout << q.front() << " ";
q.pop();
}
}
运行结果:
删除后队列=2 3
查看
#include <iostream>
#include <queue>
int main(){
//创建并初始化
deque<int> values{1, 2, 3};
queue<int> q(values);
//查看队首
cout << "队首元素为:" << q.front() << endl;
//查看队尾
cout << "队尾元素为:" << q.back() << endl;
}
运行结果:
队首元素为:1
队尾元素为:3
遍历
因为队列没有迭代器,所以队列的遍历只能是一种方式,一边查看队首,一边删除队首,直至为空。
#include <iostream>
#include <queue>
int main(){
//创建并初始化
deque<int> values{1, 2, 3};
queue<int> q(values);
//遍历
cout << "队列=";
while(!q.empty()){
cout << q.front() << " ";
q.pop();
}
}
运行结果:
/shuojia/code/C++/compiler/parse2/"learn
队列=1 2 3
stack
创建
stack的初始化不能用初始化列表,需要用其它容器来初始化
#include <iostream>
#include <stack>
int main(){
//创建并初始化
deque<int> values{1, 2, 3};
stack<int> s(values);
//展示
cout << "栈=";
while(!s.empty()){
cout << s.top() << " ";
s.pop();
}
}
运行结果:
栈=3 2 1
插入
栈的插入是栈顶插入
#include <iostream>
#include <stack>
int main(){
//创建并初始化
deque<int> values{1, 2, 3};
stack<int> s(values);
//插入
s.push(4);
//展示
cout << "插入后的栈=";
while(!s.empty()){
cout << s.top() << " ";
s.pop();
}
}
运行结果:
插入后的栈=4 3 2 1
删除
栈的删除是栈顶删除
#include <iostream>
#include <stack>
int main(){
//创建并初始化
deque<int> values{1, 2, 3};
stack<int> s(values);
//删除
s.pop();
//展示
cout << "删除后的栈=";
while(!s.empty()){
cout << s.top() << " ";
s.pop();
}
}
运行结果:
删除后的栈=2 1
查看
#include <iostream>
#include <stack>
int main(){
//创建并初始化
deque<int> values{1, 2, 3};
stack<int> s(values);
cout << "栈顶元素为:" << s.top();
}
运行结果:
栈顶元素为:3
遍历
因为stack没有迭代器,只用使用查看栈顶,删除栈顶,直到stack为空为止
#include <iostream>
#include <stack>
int main(){
//创建并初始化
deque<int> values{1, 2, 3};
stack<int> s(values);;
//展示
cout << "栈=";
while(!s.empty()){
cout << s.top() << " ";
s.pop();
}
}
运行结果:
栈=3 2 1