C++入门语法总结和STL回顾


iostream和命名空间

输入输出流

内置库iostream提供了输入和输出功能,允许开发者从键盘读取输入并在屏幕上输出结果:

#include <iostream>

iostream库包含了两个基础类型,分别是istream(输入流) 和 ostream(输出流)
在 iostream 库 中,我们有两个对象可以使用,分别是 cin 和cout:

  • cin是一个标准输入流对象,用于从键盘读取输入
  • cout是一个标准输出流对象,用于向屏幕输出结果
std::cin >> a >> b;
std::cout << a + b << std::endl;

std是命名空间,::符号是作用域操作符
当使用C++标准库的功能时,需要使用命名空间限定符来指明你要使用的内容位于哪个命名空间中。通常,会使用std::的前缀,表示正在使用C++标准库中的内容
也就是说,为了避免我们使用的变量和标准库定义的变量名称相同而引起冲突,以及避免不同标准库之间的变量名冲突,标准库定义的所有名字都在命名空间std中,如果我们要使用cin的话,就要先找到命名空间,然后再使用cin,即:std::cin

每次使用命名空间里的对象都要加上std::,就显得十分繁琐,可以在开头统一申明使用命名空间std:

using namespace std;
int main(void) {
	int a, b;
	cin >> a >> b;
	cout << a + b;
	
}

endl表示结束当前行
我们在输出结果的时候,每一个结果都要单独占一行,也就是说 每个输出结果后面要有一个回车

循环输入输出

持续输入 a 和 b,直到不输入时才会结束:

//  控制多组数据的输入和输出
while (cin >> a >> b) {

}

指定循环输入次数:

cin >> n;
while (n--) {

}

循环终止条件判断:

// 1.
while (cin >> n && n) {
	
}
//  与下方代码等效
while (cin >> n) {
	if (!n) break;
	//  ...
}

// 2.
while (n--) {
	cin >> m;
}
//  与下方代码等效
while (n-- && cin >> m) {

}

vector容器

vector,作为C++标准库中的一个容器类,表示对象的集合,它可以动态地存储一组元素,所以你可以根据需要轻松地调整 vector 的大小

包含头文件:

#include <vector>

容器创建方式:

vector<int> myVector; // 创建一个空vector, 元素是int类型的
vector<int> myVector = {1, 2, 3, 4, 5}; // 创建一个包含整数元素的容器并初始化元素
vector<int> myVector(10); // 创建一个包含10个元素的容器,元素为int类型(值被系统默认初始化为0)
vector<int> myVector(10, -1); // 创建一个包含10个重复元素的容器,每个元素的值都是-1

与数组类似,可用下标操作符[]访问 vector 中的元素:

int value = myVector[0]; // 获取第一个元素的值,即 1

内置的一些方法:

int size = myVector.size(); // 获取vector的大小
myVector.push_back(6); // 往容器的最末端添加数字6
myVector.pop_back(); // 删除vector末尾的元素
myVector.clear(); // 清空vector中的所有元素
myVector.empty(); // 判断vector是否不含有任何元素,如果长度为0,则返回真,否则,返回假

vector 容器的遍历输入:

vector<int> nums;
while (n-- && cin >> num) {
    nums.push_back(num);
}

//与下方代码等效
vector<int> nums(n, 0);
for (int i = 0; i < n; ++i) {
	cin >> nums[i];
}

string字符串

引入头文件:

#include <string>

声明和初始化:

string s1; // 默认初始化,s1是一个空的字符串
string s2 = "hello"; // 初始化一个值为hello的字符串
string s3(5, 'a') // 连续5个字符a组成的串,即'aaaaa'

字符串操作:

string s1 = "hello";
string s2 = "world";
string s3 = s1 + " " + s2; // 对字符串进行连接,拼接之后的字符串是"hello world", 中间加了空格

int length = s1.size(); // 字符串的长度即字符串中字符的个数,"hello"的长度为5
char c1 = s1[1]; // 下标从0开始,表示字符串的第一个字符
if (s1.empty()) {
  // 如果字符串为空则返回true, 否则返回false
}

输入输出string:

cin >> s;
cout << s;

因为字符串读取遇到空格就会停止,表示这是一个单词,但有的时候我们想读取完整的一行,这就要求我们的读取不会在空格处停止,这种情况下可以使用到getline(),它会一直读取字符,直到遇到换行符(Enter键)文件结束符(如果从文件读取)才结束:

string line;
// 获取用户输入的一行文本,并将其存储到line变量中
getline(cin, line);
// 输出读取的一行文本
cout << line << endl;

// 比如有多组测试用例,每组测试数据占一行
string s;
while (getline(cin, s)) {

}

cpp链表操作

空指针值通常表示为:

int* ptr = nullptr;

使用构造函数(名称与结构体名称相同,无返回类型)初始化结点:

struct ListNode {
	int val;
	ListNode* next;
	ListNode(int x) : val(x), next(nullptr) {}
};

尾插结点:

void insertNodeAtTail(ListNode* dummyNode, int val) {
    ListNode* move = dummyNode;
    while (move->next) {
        move = move->next;
    }
    ListNode* newNode = new ListNode(val);
    move->next = newNode;
}

指定位置插入结点:

inline bool insertNodeAtIndex(ListNode* dummyNode, int index, int val) {
    if (index <= 0) return false;
    ListNode* move = dummyNode;
    while (--index) {
        move = move->next;
        if (!move) return false;
    }
    ListNode* newNode = new ListNode(val);
    newNode->next = move->next;
    move->next = newNode;
    return true;
}

指定位置删除结点:

    if (index <= 0) return false;
    ListNode* move = dummyNode;
    while (--index) {
        move = move->next;
        if (!move) return false;
    }
    if (!move->next) return false;
    ListNode* tmp = move->next;
    move->next = move->next->next;
    delete tmp;
    return true;
}

打印链表:

inline void printList(ListNode* dummyNode) {
     ListNode* move = dummyNode;
     
     while (move->next) {
         move = move->next;
         cout << move->val;
         if (move->next) {
             cout << ' ';
         } else {
             cout << endl;
         }
     }
 }

打印指定位置结点:

bool printNode(ListNode* dummyNode, int index) {
    if (index <= 0) return false;
    ListNode* move = dummyNode;
    while (index--) {
        move = move->next;
        if (!move) return false;
    }
    cout << move->val << endl;
    return true;
}

hashTable哈希表

哈希表是根据关键码的值而直接进行访问的数据结构

哈希函数
通过hashCode把键值转化为数值,一般hashCode是通过特定编码方式,可以将其他数据格式转化为不同的数值

哈希碰撞
指不同的输入数据通过相同的哈希函数后生成了相同的哈希值

一般哈希碰撞有两种解决方法

拉链法:
发生冲突的元素会被存储在链表里,要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间

线性探测法:
一定要保证数据规模大于哈希表大小,放入元素位置若有冲突,会将该元素放入下一个空位位置

set集合

集合底层实现是否有序数值是否可以重复能否修改数值查询效率增删效率
set红黑树有序O(log n)O(log n)
multiset红黑树有序O(log n)O(log n)
unordered_set哈希表无序O(1)O(1)

红黑树是一种平衡二叉搜索树,所以底层实现是红黑树的set的key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加

当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset

引入头文件:

#include <set>
#include <unordered_set>

创建一个集合:

// 创建一个存储整数的无序集合
unordered_set<int> myUnorderedSet;
// 创建一个存储整数的set
set<int> mySet;
// 创建一个存储整数的 multiset
multiset<int> myMultiSet; 

插入、删除元素:

mySet.insert(7);
mySet.insert(77);
mySet.insert(777);
// 删除
mySet.erase(7);

find()方法用于查找特定元素是否存在于集合中,如果find()方法找到了要查找的元素,它会返回指向该元素的迭代器,如果未找到要查找的元素,它会返回一个 指向集合的end() 的迭代器,表示未找到:

//  表示元素i在集合中
if (mySet.find(i) != mySet.end()) {
}

map映射

集合底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
set红黑树有序O(log n)O(log n)
multiset红黑树有序O(log n)O(log n)
unordered_set哈希表无序O(1)O(1)

引入头文件:

#include <map>
#include <unordered_map>

声明一个map映射需指定键和值的类型:

// 整数类型映射到整数类型的无序映射
unordered_map<int, int> myOrderedMap;
// 将字符串映射到整数的映射
map<string, int> myMap;

插入键值对:

myOrderedMap[0] = 10;
myOrderedMap[10] = 0;

myMap["Math"] = 120;
myMap["English"] = 138;

// 如果键不存在,[]会创建创建一个新的键值对,将其插入到map中
// 并将值初始化为默认值(对于整数来说,默认值是0)

myMap.insert("Math", 120);
myMap.emplace("English", 138);
myMap.emplace("Chinese", 99);

set类似,可以使用find函数来检查某个键是否存在于map中,它会返回一个迭代器。如果键存在,迭代器指向该键值对,否则指向 map的末尾 :

// 表示键存在
if (myMap.find("English") != myMap.end()) {
}

for循环遍历map中所有键值对:

for(const pair<int, int>& kv : myMap) {
  
}

C++中的pair类型会将两个不同的值组合成一个单元, 常用于存储键值对
pair对象,两个值的类型都是int, 在使用时通过first 和 second 成员来访问 pair 中的第一个和第二个元素, 它的 first 成员存储键,而 second 成员存储值:

    for (const pair<string, int>& kv : myMap) {
        cout << kv.first << ' ' << kv.second << endl;
    }

const:这个关键字用来修饰常量,一旦变量被初始化,就不能再修改其值,表示你只能读取容器中的元素,而不能修改它们

&:这个符号表示kv是一个引用(reference),而不是值的拷贝, 如果不使用引用的话,那在每次循环迭代中都会重新创建一个新的pair对象来复制键值对,而这会导致不必要的内存分配和拷贝操作

以上数据结构都是哈希法的使用方式,即依靠键(key)来访问值(value)

范围for循环

C++11引入了范围for循环,用于更方便地遍历容器中的元素。这种循环提供了一种简单的方式来迭代容器中的每个元素,而不需要显式地使用迭代器或索引

for (类型 变量名 : 容器) {
    // 在这里使用一个变量名,表示容器中的每个元素
}

// eg:
vector<int> numbers = {1, 2, 3, 4, 5};
// 使用范围for循环遍历容器中的元素
for (int num : numbers) {
    cout << num << " ";
}

范围for循环不会修改容器中的元素,它只用于读取元素。如果需要修改容器中的元素,需要使用传统的for循环或其他迭代方式

此外,还可以使用auto关键字来让编译器自动推断元素的类型,这样代码会更通用:

for (auto num : numbers) {
    cout << num << " ";
}

stack栈

引入头文件:

#include <stack>

创建一个栈:

stack<int> stack;

栈的常用操作:

stack.push(7);
stack.push(77);
stack.push(777); // 往栈中添加元素,现在栈底元素是7,栈顶元素是777
stack.pop(); // 移除栈顶元素777,新的栈顶元素是77

int topNumber = st.top(); // 获取栈顶元素77

bool isEmpty = st.empty(); // 如果栈为空,返回true;否则返回false

int stackSize = st.size(); // 获取栈的长度(元素数量)

queue队列

队列在计算机领域中应用也十分广泛,比如在网络通信中,请求和响应通常以队列的形式进行排队,以确保数据按照正确的顺序进行传输,又比如说不同进程可以通过消息队列来传递数据和消息

引入头文件:

#include <queue>

创建一个字符串类型的队列:

queue<string> queue;

队列的常用操作:

queue.push("Jackson");
queue.push("Mickey");  // 从队尾依次入队了两个名称字符串
queue.pop(); // 移除队列头部的元素

string name = queue.front(); // 获取队列头部的元素但是不会将其移除

bool isEmpty = queue.empty(); //  如果队列为空,返回true;否则返回false

int queueSize = queue.size(); // 获取队列中元素的数量

list列表

list双向链表(doubly linked list) 实现而成,元素也存放在堆中,每个元素都是放在一块内存中,他的内存空间可以是不连续的,通过指针来进行数据的访问,这个特点使得它的随机存取变得非常没有效率,因此它没有提供[]操作符的重载。但是由于链表的特点,它可以很有效率的支持任意地方的插入和删除操作

引入头文件:

#include <list>

声明和初始化:

list<int> a; // 定义一个int类型的列表a
list<int> a(10); // 定义一个int类型的列表a,并设置初始大小为10
list<int> a(10, 1); // 定义一个int类型的列表a,并设置初始大小为10且初始值都为1
list<int> b(a); // 定义并用列表a初始化列表b
deque<int> b(a.begin(), ++a.end()); // 将列表a中的第1个元素作为列表b的初始值

// 还可以直接使用数组来初始化
int n[] = { 1, 2, 3, 4, 5 };
list<int> a(n, n + 5); // 将数组n的前5个元素作为列表a的初值

列表的基本操作:

lst.size();  // 容器大小
lst.max_size();  // 容器最大容量
lst.resize(0);  // 更改容器大小为0
lst.empty();  // 容器判空

lst.push_front(const T& x);  // 头部添加元素
lst.push_back(const T& x);  // 末尾添加元素
lst.insert(iterator it, const T& x);  // 任意位置插入一个元素
lst.insert(iterator it, int n, const T& x);  // 任意位置插入 n 个相同元素
lst.insert(iterator it, iterator first, iterator last);  // 插入另一个向量的 [first, last] 间的数据


lst.pop_front();  // 头部删除元素
lst.pop_back();  // 末尾删除元素
lst.erase(iterator it);  // 任意位置删除一个元素
lst.erase(iterator first, iterator last);  // 删除 [first,last] 之间的元素:
lst.clear();  // 清空所有元素

lst.front();  // 访问第一个元素
lst.back();  // 访问最后一个元素

类和面向对象

引用

引用就是一个对象的别名,其声明形式如下:

int x;
int& r = x;  //  声明 r 是 x 的引用

引用一旦被初始化,就不能改变引用关系,不能在作为其他对象的引用:

int x, y;
int &r = x;
//&r = y;  错误 r 不能再是别的对象的引用

声明一个引用类型变量时,必须同时初始化它,声明他是那个对象的别名,即绑定对象:

//  int &r;  错误
int x, &r = x;

指定类型的引用不能初始化到其他类型的对象上:

double f;
int& r = f;

取一个引用的地址和取一个对象的地址完全一样,都是用去地址运算:

int x, &r = x;
int* p1 = &x;
int* p2 = &r;

对象的引用就可以作为函数的参数传入:

void swap(int& a, int& b) {
	int tmp = a;
	a = b;
	b = tmp;
}

//  指针作为函数形参
void swap(int* a, int* b) {
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

函数名既代表函数,又代表函数的指针(地址):

int max(int a, int b);
int min(int a, int b);

int (*p)(int a, int b);  //  定义函数指针变量
p = max;
c = p(a, b);  //  等价于 c = max(a, b);
//  p可以指向所有与它有相同的返回值类型、参数个数、参数类型的函数(max,min)

假设 p 是指向结构体对象的指针,通过 p 访问结构体成员有两种方式:

int age = (*p).age;  //  对象法
int age = p->age;  //  指针法

所有的成员必须在类的内部声明,一旦完成类定义完成后,就没有任何其他方式可以在增加成员了

在类的外部定义成员函数:

class Data {
	private:
	  int data;
	public:
	  void set(int d);
	  int get(void) {
	  	return data;
	  }
};

// 成员函数的外部定义,使用作用域限定符::限定
void Data::set(int d) {
	data = d;  //  访问类的数据成员
}

内联成员函数:类的成员函数可以指定为inline,即内联函数
默认情况下,在类体中定义的成员函数若不包括循环等控制结构,函数体程序简单,符合内联函数体要求时,C++会自动将它们作为内联函数处理(隐式 inline)。
也可显式地将成员函数声明为 inline:

class Data {
	int getX(void) { return x; }  //  内联成员函数
	inline void setX(int _x);  //  显式指定内联成员函数
	void display(void);
	int x;

// 内联成员函数
inline void Data::set(int d) {
	x = _x;
}
// 非内联成员函数
void Data::display(void) {
	// ..
}

对象成员的引用:

class Data {
	// ...
	public:
	  int data;
	  void fun(int a, int b);
}

void caller(void) {
	//  使用对象名
	Data A;
	A.data = 100;
	A.fun(7, 77);

	//  使用对象指针
	Data A, *p1, *p2;  //  定义对象指针变量指向 A
	p1 = &A;
	p1->data = 100;
	p1->fun(7, 77);
	p2 = new Data;  //  动态分配 Data 对象
	p2->data = 100;
	p2->fun(7, 77);
	delete p2;

	// 使用对象的引用变量
	Data A, &r = A;
	r.data = 100;
	r.fun(7, 77);
}

构造函数,作用是将对象初始化为一个特定的初始状态
构造函数可以是内联函数、重载函数、带默认参数值的函数,如未在程序中声明构造函数,则系统会自动生成一个默认构造函数,其参数列表为空

下面两个都是默认构造函数,如果在类中同时出现,将产生编译错误:

Data();
Data(int x, int y, int z);

析构函数,在对象的生存期结束前的时刻系统自动调用,完成一些被删除前的清理工作
如程序中未声明析构函数,编译器将自动产生一个隐含的析构函数:

class Point {
	private:
	  int x, y;
	public:
	  Point(int _x, int _y);
	  ~Point();  //  析构函数
}

Point::Point(int _x, int _y) {
	x = _x;
	y = _y;
}

Point::~Point() {
	//  析构函数的实现
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值