目录
insert(it, x) 用来向vector的任意迭代器it处插入一个元素x, 时间复杂度为O(N)
erase()有两种用法:删除单个元素、删除一个区间内的所有元素。时间复杂度均为O(N)
1.删除单个元素:erase(it) 即删除迭代器为it 处的元素
push() front() back() pop() empty() size()
C++ 是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言,支持过程化编程、面向对象编程和泛型编程。
C++ 被认为是一种中级语言,它综合了高级语言和低级语言的特点。
C++ 是由 Bjarne Stroustrup 于 1979 年在新泽西州美利山贝尔实验室开始设计开发的。C++ 进一步扩充和完善了 C 语言,最初命名为带类的C,后来在 1983 年更名为 C++。
C++ 是 C 的一个超集,事实上,任何合法的 C 程序都是合法的 C++ 程序。
注意:使用静态类型的编程语言是在编译时执行类型检查,而不是在运行时执行类型检查。
C++基本数据类型
实际上,宽字符型数据类型所占空间相当于short int
枚举类型
枚举类型(enumeration)是C++中的一种派生数据类型,它是由用户定义的若干枚举常量的集合。
如果一个变量只有几种可能的值,可以定义为枚举(enumeration)类型。所谓"枚举"是指将变量的值一一列举出来,变量的值只能在列举出来的值的范围内。
创建枚举,需要使用关键字 enum。枚举类型的一般形式为:
enum 枚举名{ 标识符[=整型常数], 标识符[=整型常数], ... 标识符[=整型常数] } 枚举变量;如果枚举没有初始化, 即省掉"=整型常数"时, 则从第一个标识符开始。
例如,下面的代码定义了一个颜色枚举,变量 c 的类型为 color。最后,c 被赋值为 "blue"。
enum color { red, green, blue } c; c = blue;默认情况下,第一个名称的值为 0,第二个名称的值为 1,第三个名称的值为 2,以此类推。但是,您也可以给名称赋予一个特殊的值,只需要添加一个初始值即可。例如,在下面的枚举中,green 的值为 5。
enum color { red, green=5, blue };在这里,blue 的值为 6,因为默认情况下,每个名称都会比它前面一个名称大 1,但 red 的值依然为 0。
C++ 中的左值(Lvalues)和右值(Rvalues)
C++ 中有两种类型的表达式:
- 左值(lvalue):指向内存位置的表达式被称为左值(lvalue)表达式。左值可以出现在赋值号的左边或右边。
- 右值(rvalue):术语右值(rvalue)指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边。
变量是左值,因此可以出现在赋值号的左边。数值型的字面值是右值,因此不能被赋值,不能出现在赋值号的左边。下面是一个有效的语句:
int g = 20;但是下面这个就不是一个有效的语句,会生成编译时错误:
10 = 20;
在函数或一个代码块内部声明的变量,称为局部变量。它们只能被函数内部或者代码块内部的语句使用。
在所有函数外部定义的变量(通常是在程序的头部),称为全局变量。全局变量的值在程序的整个生命周期内都是有效的。
全局变量可以被任何函数访问。也就是说,全局变量一旦声明,在整个程序中都是可用的。
在程序中,局部变量和全局变量的名称可以相同,但是在函数内,局部变量的值会覆盖全局变量的值。
字符串常量
字符串字面值或常量是括在双引号 "" 中的。一个字符串包含类似于字符常量的字符:普通的字符、转义序列和通用的字符。
您可以使用 \ 做分隔符,把一个很长的字符串常量进行分行。
下面的实例显示了一些字符串常量:
实例
#include <iostream> #include <string> using namespace std; int main() { string greeting = "hello, runoob"; cout << greeting; cout << "\n"; // 换行符 string greeting2 = "hello, \ runoob"; cout << greeting2; return 0; }
hello, runoob hello, runoob
C++指针
const 指针
指向常量的指针(常量指针)
指针常量
指向常量的指针常量(常量指针常量)
下图中所有出现的阴影部分表示不能被修改
1.指向常量的指针(常量指针)
值得注意的是:定义指向常量的指针只限定指针的间接访问只能读而不能写,而没有限定指针值的读写访问性
2.指针常量
在指针定义语句的指针名前加const,表示指针本身是常量
注意在定义指针常量时必须初始化
3.指向常量的指针常量(常量指针常量)
以上便是三种const指针(注意辨析)
考虑到安全性,指针一旦初始化之后,不会轻易改动,于是指针访问数组中的元素便多用下标而不是频繁修改指针的指向。
绝大多数指针应用是指针常量和常量指针常量。这便是我们后面讲的C++引用设计的缘起
C++ 引用
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
C++ 引用 vs 指针
引用很容易与指针混淆,它们之间有三个主要的不同:
不存在空引用。引用必须连接到一块合法的内存。
一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
引用必须在创建时被初始化。指针可以在任何时间被初始化。
C++ 中创建引用
试想变量名称是变量附属在内存位置中的标签,您可以把引用当成是变量附属在内存位置中的第二个标签。因此,您可以通过原始变量名称或引用来访问变量的内容。例如:
int i = 17;我们可以为 i 声明引用变量,如下所示:
int& r = i; double& s = d;在这些声明中,& 读作引用。因此,第一个声明可以读作 "r 是一个初始化为 i 的整型引用",第二个声明可以读作 "s 是一个初始化为 d 的 double 型引用"。下面的实例使用了 int 和 double 引用:
实例
#include <iostream> using namespace std; int main () { // 声明简单的变量 int i; double d; // 声明引用变量 int& r = i; double& s = d; i = 5; cout << "Value of i : " << i << endl; cout << "Value of i reference : " << r << endl; d = 11.7; cout << "Value of d : " << d << endl; cout << "Value of d reference : " << s << endl; return 0; }
当上面的代码被编译和执行时,它会产生下列结果:
Value of i : 5 Value of i reference : 5 Value of d : 11.7 Value of d reference : 11.7
引用通常用于函数参数列表和函数返回值。
C++ 把引用作为参数
实例
#include <iostream> using namespace std; // 函数声明 void swap(int& x, int& y); int main () { // 局部变量声明 int a = 100; int b = 200; cout << "交换前,a 的值:" << a << endl; cout << "交换前,b 的值:" << b << endl; /* 调用函数来交换值 */ swap(a, b); cout << "交换后,a 的值:" << a << endl; cout << "交换后,b 的值:" << b << endl; return 0; } // 函数定义 void swap(int& x, int& y) { int temp; temp = x; /* 保存地址 x 的值 */ x = y; /* 把 y 赋值给 x */ y = temp; /* 把 x 赋值给 y */ return; }
当上面的代码被编译和执行时,它会产生下列结果:
交换前,a 的值: 100 交换前,b 的值: 200 交换后,a 的值: 200 交换后,b 的值: 100
C++ 把引用作为返回值
通过使用引用来替代指针,会使 C++ 程序更容易阅读和维护。C++ 函数可以返回一个引用,方式与返回一个指针类似。
当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。例如,请看下面这个简单的程序:
实例
#include <iostream> using namespace std; double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0}; double& setValues(int i) { double& ref = vals[i]; return ref; // 返回第 i 个元素的引用,ref 是一个引用变量,ref 引用 vals[i] } // 要调用上面定义函数的主函数 int main () { cout << "改变前的值" << endl; for ( int i = 0; i < 5; i++ ) { cout << "vals[" << i << "] = "; cout << vals[i] << endl; } setValues(1) = 20.23; // 改变第 2 个元素 setValues(3) = 70.8; // 改变第 4 个元素 cout << "改变后的值" << endl; for ( int i = 0; i < 5; i++ ) { cout << "vals[" << i << "] = "; cout << vals[i] << endl; } return 0; }
当上面的代码被编译和执行时,它会产生下列结果:
改变前的值 vals[0] = 10.1 vals[1] = 12.6 vals[2] = 33.1 vals[3] = 24.1 vals[4] = 50 改变后的值 vals[0] = 10.1 vals[1] = 20.23 vals[2] = 33.1 vals[3] = 70.8 vals[4] = 50当返回一个引用时,要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。
int& func() { int q; //! return q; // 在编译时发生错误 static int x; return x; // 安全,x 在函数作用域外依然是有效的 }
C++ 类 & 对象
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; // 高度 // 成员函数声明 double get(void); void set( double len, double bre, double hei ); }; // 成员函数定义 double Box::get(void) { return length * breadth * height; } void Box::set( double len, double bre, double hei) { length = len; breadth = bre; height = hei; } int main( ) { Box Box1; // 声明 Box1,类型为 Box Box Box2; // 声明 Box2,类型为 Box Box Box3; // 声明 Box3,类型为 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; // box 3 详述 Box3.set(16.0, 8.0, 12.0); volume = Box3.get(); cout << "Box3 的体积:" << volume <<endl; return 0; }
当上面的代码被编译和执行时,它会产生下列结果:
Box1 的体积:210 Box2 的体积:1560 Box3 的体积:1536
需要注意的是,私有的成员和受保护的成员不能使用直接成员访问运算符 (.) 来直接访问。
C++ 标准模板库 STL
C++ STL(标准模板库)是一套功能强大的 C++ 模板类,提供了通用的模板类和函数,这些模板类和函数可以实现多种流行和常用的算法和数据结构,如向量、链表、队列、栈。
C++ 标准模板库的核心包括以下三个组件:
这三个组件都带有丰富的预定义函数,帮助我们通过简单的方式处理复杂的任务。
下面的笔记借鉴了(1条消息) 蓝桥杯算法竞赛系列第0章——蓝桥必考点及标准模板库STL(上)(万字博文,建议抱走)_安然无虞的博客-CSDN博客
一、vector的常见用法详解
在介绍vector之前我们先补充“vector常用函数实例讲解”
- push_back()
- pop_back()
- size()
- clear()
- insert()
- erase()
push_back()
push_back(x) 就是在vector后面添加一个元素x, 时间复杂度为O(1)
#include<stdio.h>
#include<vector>
using namespace std;
int main()
{
vector<int> vi;
for (int i = 1; i <= 3; i++)
{
vi.push_back(i);//将1、2、3依次插入vi末尾
}
for (int i = 0; i < vi.size(); i++)//sizeof()函数会给出vi中元素的个数
{
printf("%d ", vi[i]);
}
return 0;
}
pop_back()
pop_back()用以删除vector的尾元素,时间复杂度为O(1)
#include<stdio.h>
#include<vector>
using namespace std;
int main()
{
vector<int> vi;
for (int i = 1; i <= 3; i++)
{
vi.push_back(i);//将1、2、3依次插入vi末尾
}
vi.pop_back();//删除vi的尾元素3
for (int i = 0; i < vi.size(); i++)
{
printf("%d ", vi[i]);
}
return 0;
}
. size()
size()用来获得vector中元素的个数,时间复杂度为O(1)。size()返回的是unsigned类型,应该使用%u, 不过一般来说用%d 也不会出现很大问题,这一点对所有STL容器都是一样的。
#include<stdio.h>
#include<vector>
using namespace std;
int main()
{
vector<int> vi;
for (int i = 1; i <= 3; i++)
{
vi.push_back(i);
}
printf("%d\n", vi.size());
return 0;
}
clear()
clear()用来清空vector中的所有元素,时间复杂度为O(N), 其中N为vector中元素个数
#include<stdio.h>
#include<vector>
using namespace std;
int main()
{
vector<int> vi;
for (int i = 1; i <= 3; i++)
{
vi.push_back(i);
}
vi.clear();
printf("%d\n", vi.size());//0
return 0;
}
.insert()
insert(it, x) 用来向vector的任意迭代器it处插入一个元素x, 时间复杂度为O(N)
#include<stdio.h>
#include<vector>
using namespace std;
int main()
{
vector<int> vi;
for (int i = 1; i <= 5; i++)
{
vi.push_back(i);
}
vi.insert(vi.begin() + 2, -1);//将-1插入vi[2]的位置
for (int i = 0; i < vi.size(); i++)
{
printf("%d ", vi[i]);
}
return 0;
}
.erase()
erase()有两种用法:删除单个元素、删除一个区间内的所有元素。时间复杂度均为O(N)
1.删除单个元素:erase(it) 即删除迭代器为it 处的元素
#include<stdio.h> #include<vector> using namespace std; int main() { vector<int> vi; for (int i = 5; i <= 9; i++) { vi.push_back(i);//插入元素5 6 7 8 9 } //删除8(因为vi.begin()对应的是vi[0], 所以8不是对应vi.begin() + 4) vi.erase(vi.begin() + 3); for (int i = 0; i < vi.size(); i++) { printf("%d ", vi[i]);//输出5 6 7 9 } return 0; }
2.删除一个区间内的所有元素,erase(first, last), 即删除[first, last)内的所有元素,注意哦,不包括last
#include<stdio.h> #include<vector> using namespace std; int main() { vector<int> vi; for (int i = 5; i <= 9; i++) { vi.push_back(i);//插入5、6、7、8、9 } vi.erase(vi.begin() + 1, vi.begin() + 4);//删除vi[1],vi[2],v[3] for (int i = 0; i < vi.size(); i++) { printf("%d ", vi[i]);//输出5 9 } return 0; }
如果要删除这个vector内的所有元素,正确的写法应该是vi.erase(vi.begin(), vi.end()),这正对应前面所说,vi.end()就是尾元素地址的下一个地址。当然啦,更方便的清空vector的方法是使用vi.clear()
vector翻译为向量,但是这里使用“变长数组”的叫法更容易理解,即“长度根据需要而自动改变的数组”。在考试题中,有时会碰到使用普通数组出现越界访问的情况,这样的话使用vector会让问题的解决便捷许多,而且vector本身可以作为数组使用,在一些元素个数不确定的场合可以更好的节省空间。
如果要使用vector,则需要添加vector头文件,即#include<vector>。除此之外,还需要在头文件下面加上一句"using namespace std;",这样就可以在代码中使用vector了,下面来看vector的一些常用用法。
1.vector的定义vector<typename> name;
vector<int> vi;
2.vector容器内元素的访问
vector一般有两种访问方式:通过下标访问或者通过迭代器访问。下面分别讨论这两种访问方式:
(1).通过下标访问
和访问普通的数组是一样的,对一个定义为vector<typename> vi 的vector容器来说,直接访问vi[index]即可(例如vi[0],vi[1])。当然,这里下标是从0 ~ vi.size() - 1。(2).通过迭代器访问
迭代器(iterator)可以理解为一种类似指针的东西,其定义是:vector<typename>::iterator it;
例如
vector<int>::iterator it; vector<double>::iterator it;
这样就得到了迭代器it,并且可以通过*it来访问vector里的元素。
#include<stdio.h> #include<vector> using namespace std; int main() { vector<int> vi; for (int i = 1; i <= 5; i++) { vi.push_back(i);//push_back(i)在vi的末尾添加元素i,即依次添加1 2 3 4 5 } //vi.begin()为取vi得首元素地址,而it指向这个地址 vector<int>::iterator it = vi.begin(); for (int i = 0; i < 5; i++) { printf("%d ", *(it + i));//输出vi[i] } return 0; } //输出1 2 3 4 5
不难看出,vi[i] 和 *(vi.begin() + i) 是等价的
既然上面说到了begin()函数的作用是取vi的首元素地址,那么这里还要提到end()函数。不过,和begin()不同的是,end()并不是取vi尾元素的地址,而是取尾元素地址的下一个地址。end()作为迭代器的末尾标志,不储存任何元素。米国人思维比较习惯左闭右开,在这里begin()和end()也是如此。
可能概念听起来太生硬,那我就用三种遍历方式促进大家对上面迭代器的理解。
第一种遍历方式:利用while循环,定义起始迭代器和结束迭代器。
#include<stdio.h> #include<vector> using namespace std; int main() { vector<int> vi; vi.push_back(10);//push_back()尾插 vi.push_back(20); vi.push_back(30); vi.push_back(40); vector<int>::iterator iBegin = vi.begin();//vi.begin()起始迭代器--指向容器的第一个元素(指针) vector<int>::iterator iEnd = vi.end();//vi.end()结束迭代器(指向容器的最后一个元素的下一个位置)!!!这里需要特别注意一下 while (iBegin != iEnd) { printf("%d ", *(iBegin)); iBegin++; } return 0; }
第二种遍历方式:利用for循环
#include<stdio.h> #include<vector> using namespace std; int main() { vector<int> vi; for (int i = 10; i < 50; i += 10) { vi.push_back(i); } for (vector<int>::iterator it = vi.begin(); it != vi.end(); it++)//起始迭代器-取出容器首元素的地址,而it指向这个地址 { printf("%d ", *it); } return 0; }
值得注意的是:vector的迭代器不支持it < vi.end()写法,因此循环条件只能用 it != vi.end()
第三种遍历方式:利用专门的遍历算法
#include<stdio.h> #include<vector> #include<algorithm> using namespace std; void myPrint(int val) { printf("%d ", val); } int main() { vector<int> v; for (int i = 10; i < 50; i += 10) { v.push_back(i); } for_each(v.begin(), v.end(), myPrint); //for_each() 是一个非常有用的函数,它有助于在 STL 容器中的每个元素上调用函数 。 return 0; }
值得注意的是:最后需要指出的是,在常用STL容器中,只有在vector和string中,才允许使用vi.begin()+3 这种迭代器加上整数的写法。
set 的常见用法详解
set翻译为集合,是一个内部自动有序且不含重复元素的容器。在考试中,有可能出现需要去掉重复元素的情况,而且有可能因这些元素比较大或者类型不是int型而不能直接开散列表,在这种情况下就可以用set来保留元素本身而不去考虑它的个数。当然,上面说的情况也可以通过再开一个数组进行下标和元素的对应来解决,但是set提供了更为直观的接口,并且加入set之后可以实现自动排序,因此熟练使用set可以在做某些题时减少思维量。
set最主要的作用是自动去重并按升序排序,因此当我们碰到去重但是不方便直接开数组的情况,可以尝试用set解决。
如果要使用set,需要添加set 头文件,即#include<set> 。除此之外,还需要在头文件下面加上一句:"using namespace std;" ,这样就可以在代码中使用set了。
set的定义
set<typename> name;
set<int> st;
set容器内元素的访问
(注意set只能通过迭代器(iterator)访问)
set<typename>::iterator it;
set<int>::iterator:: it;
这样就得到了it, 并且可以通过*it 来访问set 里的元素
由于除了vector 和 string 之外的STL容器都不支持*(it + i) 的访问方式,因此只能按如下方式枚举:
#include<stdio.h> #include<set> using namespace std; int main() { set<int> st; st.insert(3);//insert(x)将x插入set中 st.insert(5); st.insert(2); st.insert(3); //注意,不支持it < st.end()的写法 for (set<int>::iterator it = st.begin(); it != st.end(); it++) { printf("%d ", *it);//输出2 3 5 } return 0; }
set常用函数实例解析
insert()
find()
erase()
size()
clear()
(1).insert()
insert(x),将x插入set容器中,并自动递增排序和去重,时间复杂度为O(logN),其中N为set容器内元素的个数,注意insert()和vector中用法的不同
(2).find()
find(value), 返回set中对应值为value的迭代器,时间复杂度为O(logN),N为set内的元素个数
#include<stdio.h> #include<set> using namespace std; int main() { set<int> st; for (int i = 1; i <= 3; i++) { st.insert(i); } set<int>::iterator it = st.find(2);//在set中查找2,返回其迭代器 printf("%d\n", *it);//输出200 //也可以写成printf("%d\n", *(st.find(2)); return 0; }
(3).erase()
erase()有两种用法:删除单个元素、删除一个区间内的所有元素
1.删除单个元素:删除单个元素也有两种方法
方法一:st.erase(it), it为所需要删除元素的迭代器。时间复杂度为O(1)。结合find()函数来使用
#include<stdio.h> #include<set> using namespace std; int main() { set<int> st; st.insert(100); st.insert(200); st.insert(100); st.insert(300); st.erase(st.find(100));//利用find()函数找到100,然后用erase()删除它 st.erase(st.find(200));//利用find()函数找到200,然后用erase()删除它 for (set<int>::iterator it = st.begin(); it != st.end(); it++) { printf("%d\n", *it);//输出300 } return 0; }
方法二:st.erase(value), value为所需要删除元素的值。时间复杂度为O(logN),N为set内的元素个数。
#include<stdio.h> #include<set> using namespace std; int main() { set<int> st; st.insert(100); st.insert(200); st.erase(100); for (set<int>::iterator it = st.begin(); it != st.end(); it++) { printf("%d\n", *it);//输出200 } return 0; }
值得注意的是:删除一个区间内的所有元素:st.erase(first, last)可以删除一个区间内的所有元素,其中first为所需要删除区间的起始迭代器,而last则为所需要删除区间的末尾迭代器的下一个地址,即为删除[first, last),时间复杂度为O(last - first)。注意哦,不包括last。
#include<stdio.h> #include<set> using namespace std; int main() { set<int> st; st.insert(20); st.insert(10); st.insert(40); st.insert(30); set<int>::iterator it = st.find(30); st.erase(it, st.end());//删除元素30至末尾之间的元素,即30和40 for (it = st.begin(); it != st.end(); it++) { printf("%d ", *it);//输出10 20 } return 0; }
(4).size()
size()用来获得set内的元素个数,时间复杂度为O(1)
#include<stdio.h> #include<set> using namespace std; int main() { set<int> st; st.insert(2); st.insert(5); st.insert(4); printf("%d\n", st.size());//输出3 return 0; }
(5).clear()
clear()用来清空set中的所有元素,时间复杂度为O(N),其中N为set内的元素个数
示例如下:
#include<stdio.h> #include<set> using namespace std; int main() { set<int> st; st.insert(2); st.insert(5); st.insert(4); st.clear();//清空set printf("%d\n", st.size());//输出0 return 0; }
一、string的常见用法详解
在C语言中,一般使用字符数组char str[ ] 来存放字符串,但是使用字符数组有时会显得操作麻烦,而且容易因经验不足产生错误,得不偿失。为了使编程者可以更方便的对字符串进行操作,C++在STL中加入了string类型,对字符串常用的需求功能进行了封装,使得操作起来更方便,且不易出错。
如果要使用string,需要添加string头文件,即#include<string>(注意string.h 和 string 是不一样的头文件)。除此之外,还需要在头文件下面加上一句:"using namespace std;", 这样就可以在代码中使用string了。下面来看string的一些常见用法。
1.string的定义
定义string的方式跟基本数据类型相同,只需要在string后面跟上变量名即可:
string str;
如果要初始化,可以直接给string类型的变量进行赋值:
string str = "abcd"
2.string中内容的访问
(1).通过下标访问一般来说,可以直接像字符数组那样去访问string:
#include<stdio.h> #include<string> using namespace std; int main() { string str = "abcd"; for (int i = 0; i < str.length(); i++) { printf("%c ", str[i]);//输出a b c d } return 0; }
注意哦,如果要读入和输出整个字符串,则只能用cin 和 cout:
#include<iostream>//cin和cout在iostream头文件中,而不是stdio.h #include<string> using namespace std; int main() { string str; cin >> str; cout << str; return 0; }
上面的代码对任意的字符串输入,都会输出同样的字符串。
那么,真的没有办法用printf来输出string吗?其实是有的,即用c_str()将string类型转换为字符数组进行输出,示例如下:
#include<stdio.h> #include<string> using namespace std; int main() { string str = "abcd"; printf("%s\n", str.c_str());//将string型str使用c_str()变成字符数组 return 0; }
(3).通过迭代器访问
一般仅通过(1)即可满足访问的要求,但是有些函数比如insert()与erase()则要求以迭代器为参数,因此还是需要学习一下string迭代器的用法。
由于string不像其他STL容器那样需要参数,因此可以直接入下定义:
string::iterator it;
这样就得到了迭代器it, 并且可以通过*it 来访问string里的每一位:
#include<stdio.h> #include<string> using namespace std; int main() { string str = "abcd"; for (string::iterator it = str.begin(); it != str.end(); it++) { printf("%c ", *it);//输出a b c d } return 0; }
最后指出,string和vector一样,支持直接对迭代器进行加减某个数字,如str.begin() + 3的写法是可行的
3.string常用函数实例解析
operator+=
compare operator
length() / size()
insert()
erase()
clear()
substr()
string::nops
find()
replace()
(1).operator+=
这是string的加法,可以将两个string直接拼接起来
示例如下:
#include<iostream> #include<string> using namespace std; int main() { string str1 = "abc", str2 = "xyz", str3; str3 = str1 + str2;//将str1和str2拼接,赋值给str3 str1 = str1 + str2;//将str2直接拼接到str1上 cout << str1 << endl;//输出abcxyz cout << str3 << endl;//输出abcxyz return 0; }
(2).compare operator(比较操作符)
两个string类型可以直接使用==、!=、<、<=、>、>=比较大小,比较规则是字典序。
示例如下:
#include<stdio.h> #include<string> using namespace std; int main() { string str1 = "aa", str2 = "aaa", str3 = "abc", str4 = "xyz"; if (str1 < str2)//如果str1字典序小于str2,输出ok1 { printf("ok1\n");//输出ok1 } if (str1 != str3)//如果str1和str3字典序不等,输出ok2 { printf("ok2\n");//输出ok2 } if (str4 >= str3)//如果str4字典序大于等于str3,输出ok3 { printf("ok3\n");//输出ok3 } return 0; }
(3).length() / size()
length()返回string的长度,即存放的字符数。时间复杂度为O(1)。size()与length()基本相同
示例如下:
string str = "abcdef"; printf("%d %d\n", str.length(), str.size());//输出6 6
(4).insert()
string的insert()函数有很多种写法,这里给出几种常用的写法。时间复杂度为O(N)
1.insert(pos, string), 在pos号位置插入一个字符串string
示例如下:
string str = "abcxyz", str2 = "opq"; str.insert(3, str2);//往str[3]处插入opq,将括号里的str2直接写成"opq"也是可以的 cout<<str<<endl;//输出abcopqxyz
2.insert(it, it2, it3), it 为原字符串的欲插入位置,it2 和 it3 为待插字符串的首尾迭代器,用来表示串[it2, it3)将被插在it 的位置上
示例如下:
#include<iostream> #include<string> using namespace std; int main() { string str = "abcxyz", str2 = "opq";//str为原字符串,str2为待插字符串 //在str的3号位(即c和x之间)插入str2 str.insert(str.begin() + 3, str2.begin(), str2.end()); cout << str << endl;//输出abcopqxyz return 0; }
(5).erase()
erase()有两种用法:删除单个元素、删除一个区间内的所有元素。时间复杂度均为O(N)
1.删除单个元素:str.erase(it) 用于删除单个元素,it为需要删除的元素的迭代器
示例如下:
#include<iostream> #include<stdio.h> using namespace std; int main() { string str = "abcdefg"; str.erase(str.begin() + 4);//删除4号位(即e) cout << str << endl;//输出abcdfg return 0; }
2.删除一个区间内的所有元素:有两种方法:
str.erase(first, last), 其中first为需要删除的区间的起始迭代器,而last为需要删除的区间的末尾迭代器的下一个地址,即为删除[first, last)
示例如下:
#include<iostream> #include<string> using namespace std; int main() { string str = "abcdefg"; //删除在[str.begin() + 2, str.end() - 1)内的元素,即cdef str.erase(str.begin() + 2, str.end() - 1); cout << str << endl;//输出abg return 0; }
str.erase(pos, length), 其中pos为需要开始删除的起始位置,length为删除的字符个数。
示例如下:
#include<iostream> #include<string> using namespace std; int main() { string str = "abcdefg"; str.erase(3, 2);//删除de cout << str << endl;//输出abcfg return 0; }
(6).clear()
clear()可以清空string中的数据,时间复杂度一般为O(1)
示例如下:
#include<iostream> #include<string> using namespace std; int main() { string str = "abcd"; str.clear();//清空字符串 cout << str.length() << endl;//输出0 return 0; }
(7).substr()
substr(pos, len) 返回从pos号位开始、长度为len的子串,时间复杂度为O(len)
示例如下:
#include<iostream> #include<string> using namespace std; int main() { string str = "Thank you for your smile."; cout << str.substr(0, 5) << endl;//输出Thank cout << str.substr(14, 4) << endl;//输出your cout << str.substr(19, 5) << endl;//输出smile return 0; }
(8).string::npos
string::npos是一个常数,其本身的值为-1 ,但由于是unsigned int 类型,因此实际上也可以认为是unsigned int 类型的最大值,可认为是4,294,967,295。string::npos 用以作为 find 函数失配时的返回值。
(9).find()
str.find(str2) 当str2 是str 的子串时,返回其在str 中第一次出现的位置,如果str2 不是str 的子串,那么返回string::npos
str.find(str2, pos), 从str 的pos 号位开始匹配str2,返回值与上相同。时间复杂度为O(M*N),M和N 分别是str2 和str的长度
示例如下:
#include<iostream> #include<string> using namespace std; int main() { string str = "Thank you for your smile"; string str2 = "you"; string str3 = "me"; if (str.find(str2) != string::npos) { cout << str.find(str2) << endl;//输出6 } if (str.find(str2, 7) != string::npos) { cout << str.find(str2, 7) << endl;//输出14 } if (str.find(str3) != string::npos) { cout << str.find(str3) << endl; } else { cout << "I know there is no position for me." << endl; } return 0; }
(10).replace()
str.replace(pos,len,str2) 把str 从pos 号位开始、长度为len 的子串替换为上str2
str.replace(it1,it2,str2) 把str 的迭代器[it1, it2)范围的子串替换为str2
示例如下:
#include<iostream> #include<string> using namespace std; int main() { string str = "Maybe you will turn around."; string str2 = "will not"; string str3 = "surely"; cout << str.replace(10, 4, str2) << endl; cout << str.replace(str.begin(), str.begin() + 5, str3) << endl; return 0; }
queue的常见用法详解
queue翻译为队列,在STL中主要则是实现一个先进先出的容器,当需要实现广度优先搜索时,可以不用自己手动实现一个队列,而是用queue代替,以提高程序的准确性。
1.queue的定义
要使用queue, 需要先添加头文件#include<queue>, 并在头文件下面加上"using namespace std;" ,然后就可以使用了。
其定义的写法和其他STL容器相同,typename 可以是任意基本数据类型和容器:
queue<typename> name;
2.queue容器内元素的访问
由于队列(queue)本身就是一种先进先出的限制性数据结构,因此在STL中只能通过front() 来访问队首元素,或是通过back() 来访问队尾元素。
示例如下:
#include<stdio.h> #include<queue> using namespace std; int main() { queue<int> q; for (int i = 1; i <= 5; i++) { q.push(i);//push(i)用以将i压入队列,因此依次入队1 2 3 4 5 } printf("%d %d\n", q.front(), q.back());//输出结果为1 5 return 0; }
3.queue常用函数实例解析
push()
front()
back()
pop()
empty()
size()
(1).push()
push(x) 将x 进行入队,时间复杂度为O(1)
(2).front(), back()
front(), back()可以分别获得队首元素和队尾元素,时间复杂度为O(1)
注意哦,使用front() 和 pop() 函数之前,必须用empty() 判断队列是否为空,否则可能会因为队列空导致错误
(3).pop()
pop()令队首元素出队,时间复杂度为O(1)
示例如下:
#include<stdio.h> #include<queue> using namespace std; int main() { queue<int> q; for (int i = 1; i <= 5; i++) { q.push(i); } for (int i = 0; i < 3; i++) { q.pop();//出队列元素3次,依次出队1 2 3 } printf("%d\n", q.front());//输出4 return 0; } (4).empty()
empty()检测queue是否为空,返回true则为空,返回false则非空,时间复杂度为O(1)
示例如下:
#include<stdio.h> #include<queue> using namespace std; int main() { queue<int> q; if (q.empty() == true)//一开始队列里没有元素,所以是空 { printf("Empty\n"); } else { printf("Not Empty\n"); } q.push(1); if (q.empty() == true) { printf("Empty\n"); } else { printf("Not Empty\n"); } return 0; }
(5).size()
size()返回queue内元素的个数,时间复杂度为O(1)
示例如下:
#include<stdio.h> #include<queue> using namespace std; int main() { queue<int> q; for (int i = 1; i <= 5; i++) { q.push(i); } printf("%d\n", q.size());//输出5 return 0; }
【延伸】:STL容器中还有两种容器跟队列有关,分别是双端队列(deque) 和优先队列(priority_queue) ,前者是首尾皆可插入和删除的队列,后者是使用堆实现的默许将当前队列最大元素置于队首的容器,这里暂时先不介绍,后期如果需要再进行补充。
deque的常见用法详解
deque容器为一个给定类型的元素进行线性处理,像向量一样,它能够快速地随机访问任一个元素,并且能够高效地插入和删除容器的尾部元素。但它又与vector不同,deque支持高效插入和删除容器的头部元素,因此也叫做双端队列。deque类常用的函数如下:
(1) 构造函数
deque():创建一个空deque
deque(int nSize):创建一个deque,元素个数为nSize
deque(int nSize,const T& t):创建一个deque,元素个数为nSize,且值均为t
deque(const deque &):复制构造函数
(2) 增加函数
void push_front(const T& x):双端队列头部增加一个元素X
void push_back(const T& x):双端队列尾部增加一个元素x
iterator insert(iterator it,const T& x):双端队列中某一元素前增加一个元素x
void insert(iterator it,int n,const T& x):双端队列中某一元素前增加n个相同的元素x
void insert(iterator it,const_iterator first,const_iteratorlast):双端队列中某一元素前插入另一个相同类型向量的[forst,last)间的数据
(3) 删除函数
Iterator erase(iterator it):删除双端队列中的某一个元素
Iterator erase(iterator first,iterator last):删除双端队列中[first,last)中的元素
void pop_front():删除双端队列中最前一个元素
void pop_back():删除双端队列中最后一个元素
void clear():清空双端队列中最后一个元素
(4) 遍历函数
reference at(int pos):返回pos位置元素的引用
reference front():返回手元素的引用
reference back():返回尾元素的引用
iterator begin():返回向量头指针,指向第一个元素
iterator end():返回指向向量中最后一个元素下一个元素的指针(不包含在向量中)
reverse_iterator rbegin():反向迭代器,指向最后一个元素
reverse_iterator rend():反向迭代器,指向第一个元素的前一个元素
(5) 判断函数
bool empty() const:向量是否为空,若true,则向量中无元素
(6) 大小函数
Int size() const:返回向量中元素的个数
int max_size() const:返回最大可允许的双端对了元素数量值
(7) 其他函数
void swap(deque&):交换两个同类型向量的数据
void assign(int n,const T& x):向量中第n个元素的值设置为x
下面给出一个实例代码帮助理解
#include "stdafx.h" #include<iostream> #include<deque> using namespace std; int _tmain(int argc, _TCHAR* argv[]) { deque<int> d; d.push_back( 10 ); d.push_back(20); d.push_back(30); cout<<"原始双端队列:"<<endl; for(int i = 0; i < d.size(); i++) { cout<<d.at(i)<<"\t"; } cout<<endl; d.push_front(5); d.push_front(3); d.push_front(1); cout<<"after push_front(5.3.1):"<<endl; for(int i = 0;i < d.size();i++) { cout<<d.at(i)<<"\t"; } cout<<endl; d.pop_front(); d.pop_front(); cout<<"after pop_front() two times:"<<endl; for(int i = 0;i < d.size();i++) { cout<<d.at(i)<<"\t"; } cout<<endl; return 0; }
stack的常见用法详解
stack 翻译为栈,是STL中实现的一个先进后出的容器,stack 用来模拟实现一些递归,防止程序对栈内存的限制而导致程序运行出错。1.stack的定义
要使用stack,应先添加头文件#include<stack>, 并在头文件下面加上" using namespace std;",然后就可以使用了。其定义的写法和其他STL容器相同,typename可以是任意基本数据类型或容器:
stack<typename> name;
2.stack容器内元素的访问
由于栈(stack) 本身就是一种先进后出的数据结构,在STL的stack 中只能通过top() 来访问栈顶元素示例如下:
#include<stdio.h> #include<stack> using namespace std; int main() { stack<int> st; for (int i = 1; i <= 5; i++) { st.push(i);//依次入栈1 2 3 4 5 } printf("%d\n", st.top());//输出5 return 0; }
3.stack常用函数实例解析
push()
top()
pop()
empty()
size()
(1).push()
push(x) 将x 入栈,时间复杂度为O(1),(2).top()
top()获得栈顶元素,时间复杂度为O(1)(3).pop()
pop()用以弹出栈顶元素,时间复杂度为O(1)示例如下:
#include<stdio.h> #include<stack> using namespace std; int main() { stack<int> st; for (int i = 1; i <= 5; i++) { st.push(i); } for (int i = 0; i < 3; i++) { st.pop(); } printf("%d\n", st.top());//输出2 return 0; }
(4).empty()
empty()可以检测stack 内是否为空,返回true 为空,返回false 为非空,时间复杂度为O(1)(5).size()
size()返回stack 内元素的个数,时间复杂度为O(1)
————————————————
以上笔记出于https://blog.csdn.net/weixin_57544072/article/details/121383654
list 实现
list 容器是由双向链表实现的,因此不能使用下标运算符 [] 访问其中的元素。使用 list 的时候得加上 #include <list> 头文件以及得在 std 名字空间中使用。
list 定义和初始化
只需要简单的list<TYPE> my_list;
就可以完成对一个 list 的定义了。不需要 new。
初始化的话就要用到 list 的构造函数。
一个简单的例子是int myints[] = {75,23,65,42,13}; list<int> mylist (myints, myints+5);
禁止出现以下写法!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
会出现死循环!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
int main () { int myints[]= {17,89,7,14}; list<int> mylist (myints,myints+4); // 17 89 7 14 cout << "mylist contains:"; for (list<int>::iterator it=mylist.begin(); it!=mylist.end(); ++it) { if (*it == 89) mylist.remove(89); cout << ' ' << *it; } cout << '\n'; // 死循环 return 0; }
要想这样写可使用erase()
algorithm头文件下的常用函数
使用algorithm 头文件,需要在头文件下面加上一行"using namespace std;",才能正常使用
max()、min()、abs()
swap()
reverse()
next_permutation()
fill()
sort()
lower_bound() 和 upper_bound()
1.max()、min()和abs()max(x,y)和min(x,y) 分别返回x, y中的最大值和最小值,且参数必须是两个,可以是浮点数,如果想返回三个数x,y,z的最大值,可以使用max(x, max(y, z)) 的写法;abs(x) 返回x的绝对值。注意:此时的x 必须是整数,浮点数的绝对值请用math 头文件下的fabs
示例如下:
#include<stdio.h> #include<algorithm> using namespace std; int main() { int x = -1; int y = -2; printf("%d %d\n", max(x, y), min(x, y));//输出-1 -2 printf("%d %d\n", abs(x), abs(y));//输出1 2 return 0; } 2.swap() swap(x, y) 用来交换x 和 y 的值 示例如下: #include<stdio.h> #include<algorithm> using namespace std; int main() { int x = 10; int y = 20; swap(x, y); printf("%d %d\n", x, y);//输出20 10 return 0; }
3.reverse()
reverse(it, it2) 可以将数组指针在[it, it2) 之间的元素或容器的迭代器在[it, it2) 范围内的元素进行反转示例如下:
#include<stdio.h> #include<algorithm> using namespace std; int main() { int arr[10] = { 10,11,12,13,14,15 }; reverse(arr, arr + 4);//将arr[0]~arr[3]反转 for (int i = 0; i < 6; i++) { printf("%d ", arr[i]);//输出13,12,11,10,14,15 } return 0; }
如果要是对容器中的元素(例如string 字符串)进行反转,结果也是一样#include<stdio.h> #include<string> #include<algorithm> using namespace std; int main() { string str = "abcdefghi"; reverse(str.begin() + 2, str.begin() + 6);//对str[0]~str[5]反转 for (int i = 0; i < str.length(); i++) { printf("%c", str[i]);//输出abfedcghi } return 0; }
4.next_permutation()
next_permutation() 给出一个序列在全排列中得下一个序列例如,当n == 3 时的全排列为:
123
132
213
231
312
321
这样231的下一个序列就是312
示例如下:
#include<stdio.h> #include<algorithm> using namespace std; int main() { int a[10] = { 1,2,3 }; //a[0]~a[2]之间的序列需要求解next_permutation do { printf("%d%d%d\n", a[0], a[1], a[2]); } while (next_permutation(a, a + 3)); return 0; }
在上述的代码中,使用循环是因为next_permutation在已经到达全排列的最后一个时会返回false, 这样会方便退出循环。而使用do...while语句而不使用while语句是因为序列1 2 3本身也需要输出,如果使用while会直接跳到下一个序列再输出,这样的话结果会少一个123注意:next_permutation(start, end) 是左闭右开的!
5.fill()
fill()可以把数组或容器中的某一段区间赋为某个相同的值。和memset 不同,这里的赋值可以是数组类型对应范围中的任意值示例如下:
#include<stdio.h> #include<algorithm> using namespace std; int main() { int a[5] = { 1,2,3,4,5 }; fill(a, a + 5, 133);//将a[0]~a[4]均赋值为133 for (int i = 0; i < 5; i++) { printf("%d ", a[i]);//输出133 133 133 133 133 } return 0; }
6.sort()
顾名思义,sort()就是用来排序的函数,它根据具体情形使用不同的排序方法,效率较高。一般来说,不推荐使用C语言中的qsort函数,原因是qsort 用起来比较繁琐,涉及很多指针的操作。如何使用sort排序?
sort函数的使用必须加上头文件"#include<algorithm>" 和 "using namespace std;",其使用的方式如下:sort(首元素地址(必填),尾元素地址的下一个地址(必填),比较函数(非必填));
可以看到,sort的参数有三个,其中前两个是必填的,而比较函数则可以根据需要填写,如果不写比较函数,则默认对前面给出的区间进行递增排序。
示例如下:
#include<stdio.h> #include<algorithm> using namespace std; int main() { int a[6] = { 9,4,2,5,6,-1 }; //将a[0]~a[3]进行从小到大排序 sort(a, a + 4); for (int i = 0; i < 6; i++) { printf("%d ", a[i]);//输出2 4 5 9 6 -1 } putchar('\n'); //将a[0]~a[5]进行从小到大排序 sort(a, a + 6); for (int i = 0; i < 6; i++) { printf("%d ", a[i]);//输出-1 2 4 5 6 9 } return 0; }
特别需要注意理解的是尾元素地址的下一个地址!对double数组进行排序:
#include<stdio.h> #include<algorithm> using namespace std; int main() { double a[] = { 1.4,-2.1,9 }; sort(a, a + 3); for (int i = 0; i < 3; i++) { printf("%.1lf ", a[i]); } return 0; }
对char型数组进行排序(默认是字典序)示例如下:
#include<stdio.h> #include<algorithm> using namespace std; int main() { char c[] = { 'T', 'W','A', 'K' }; sort(c, c + 4); for (int i = 0; i < 4; i++) { printf("%c", c[i]);//输出AKTW } return 0; }
我们需要知道的是,如果对序列进行排序,那么序列中的元素一定要有可比性,因此需要制定排序规则来建立这种可比性。特别是像结构体,本身并没有大小关系,需要认为制定比较的规则。sort 的第三个可选参数就是cmp函数,用来实现这个规则。如何实现比较函数cmp
下面介绍对基本数据类型、结构体类型、STL容器进行自定义规则排序时cmp的写法。<1>.基本数据类型数组的排序
若比较函数不填,则默认按照从小到大的顺序排序。示例如下:
#include<stdio.h> #include<algorithm> using namespace std; int main() { int a[] = { 3,1,4,2 }; sort(a, a + 4); for (int i = 0; i < 4; i++) { printf("%d ", a[i]);//输出1 2 3 4 } return 0; }
如果想要从大到小来排序,则要使用比较函数cmp 来“告诉”sort 何时要交换元素(让元素的大小比较关系反过来)示例如下:
#include<stdio.h> #include<algorithm> using namespace std; bool cmp(int a, int b) { return a > b;//可以理解为当a>b时把a放在b前面 } int main() { int a[] = { 3,1,4,2 }; sort(a, a + 4, cmp); for (int i = 0; i < 4; i++) { printf("%d ", a[i]);//输出4 3 2 1 } return 0; }
这样就可以让数值较大的元素放在前面,也就达到了从大到小排序的要求。同样的,对double型数组从大到小排序的代码如下:
#include<stdio.h> #include<algorithm> using namespace std; bool cmp(double a, double b) { return a > b;//同样是a>b } int main() { double a[] = { 1.4,-2.1,9 }; sort(a, a + 3, cmp); for (int i = 0; i < 3; i++) { printf("%.1lf ", a[i]);//输出9.0 1.4 -2.1 } return 0; } 对char型数组从大到小排序如下: #include<stdio.h> #include<algorithm> using namespace std; bool cmp(char a, char b) { return a > b;//可以理解为当a>b时把a放在b之前 } int main() { char c[] = { 'T','W','A','K' }; sort(c, c + 4, cmp); for (int i = 0; i < 4; i++) { printf("%c", c[i]);//输出WTKA } return 0; }
【记忆方法】:如果要把数据从小到大排列,那么就用'<', 因为"a<b" 就是左小右大;如果要把数据从大到小排列,那么就用'>', 因为"a>b" 就是左大右小。而当不确定或者忘记的时候,不妨两种都试一下,就会知道该用哪种了。
<2>.结构体数组的排序
现在定义了如下结构体:struct node{ int x, y; }ssd[10];
如果想将ssd数组按照 x 从大到小排序(即进行一级排序),那么可以这样写cmp函数:bool cmp(node a, node b){ return a.x > b.x; }
示例如下:#include<stdio.h> #include<algorithm> using namespace std; struct node { int x; int y; }ssd[10]; bool cmp(node a, node b) { return a.x > b.x;//按x值从大到小对结构体数组进行排序 } int main() { ssd[0].x = 2; ssd[0].y = 2; ssd[1].x = 1; ssd[1].y = 3; ssd[2].x = 3; ssd[2].y = 1; sort(ssd, ssd + 3, cmp); for (int i = 0; i < 3; i++) { printf("%d %d\n", ssd[i].x, ssd[i].y); } return 0; }
而如果想先按x 从大到小排序,但当x相等的情况下,按照y的大小从小到大来排序(即进行二级排序),那么cmp的写法是:bool cmp(node a, node b) { if(a.x != b.x) { return a.x > b.x; } else { return a.y < b.y; } }
这里的cmp函数首先判断结构体内的x 元素是否相等,如果不相等则直接按照x 的大小来排序,否则,按照y 的大小来排序。示例如下:
#include<stdio.h> #include<algorithm> using namespace std; struct node { int x; int y; }ssd[10]; bool cmp(node a, node b) { if (a.x != b.x) { return a.x > b.x;//x 不等时按x 从大到小排序 } else { return a.y < b.y;//x 相等时按y 从小到大排序 } } int main() { ssd[0].x = 2; ssd[0].y = 2; ssd[1].x = 1; ssd[1].y = 3; ssd[2].x = 3; ssd[2].y = 1; sort(ssd, ssd + 3, cmp);//排序 for (int i = 0; i < 3; i++) { printf("%d %d\n", ssd[i].x, ssd[i].y); } return 0; }
<3>.容器的排序
在STL标准容器中,只有vector、string、deque是可以使用sort的。这是因为像set、map这种容器是用红黑树实现的(了解即可),元素本身有序,故不允许使用sort排序vector示例如下:
#include<stdio.h> #include<vector> #include<algorithm> using namespace std; bool cmp(int a, int b)//因为vector中的元素为int型,因此仍然是int的比较 { return a > b; } int main() { vector<int> vi; vi.push_back(3); vi.push_back(1); vi.push_back(2); sort(vi.begin(), vi.end(), cmp); for (vector<int>::iterator it = vi.begin(); it != vi.end(); it++) { printf("%d ", *it);//输出3 2 1 } return 0; }
再来看string 的排序,示例如下:#include<iostream> #include<string> #include<algorithm> using namespace std; int main() { string str[3] = { "bbbb", "cc", "aaa" }; sort(str, str + 3);//将string数组按字典树从小到大输出 for (int i = 0; i < 3; i++) { cout << str[i] << endl; } return 0; }
如果上面这个例子中,需要按照字符串长度从小到大排序,那么可以这样写:#include<iostream> #include<string> #include<algorithm> using namespace std; bool cmp(string str1, string str2) { return str1.length() < str2.length();//按照string 的长度从小到大排序 } int main() { string str[3] = { "bbbb", "cc", "aaa" }; sort(str, str + 3, cmp); for (int i = 0; i < 3; i++) { cout << str[i] << endl; } return 0; }
7.lower_bound()和upper_bound()
lower_bound() 和 upper_bound() 需要用在一个有序数组或容器中lower_bound(first, last, val) 用来寻找在数组或容器的[first, last) 范围内第一个值大于等于val元素的位置,如果是数组,则返回该位置的指针;如果是容器,则返回该位置的迭代器。
upper_bound(first, last, val) 用来寻找在数组或容器的[first, last) 范围内第一个值大于val 的元素的位置,如果是数组,则返回该位置的指针;如果是容器,则返回该位置的迭代器。
显然,如果数组或容器中没有需要寻找的元素,则lower_bound() 和 upper_bound() 均返回可以插入该元素的位置的指针或迭代器(即假设存在该元素时,该元素应当在的位置)。
示例如下:
#include<stdio.h> #include<algorithm> using namespace std; int main() { int a[10] = { 1,2,2,3,3,3,5,5,5,5 }; //寻找-1 int* lowerPos = lower_bound(a, a + 10, -1); int* upperPos = upper_bound(a, a + 10, -1); printf("%d %d\n", lowerPos - a, upperPos - a);//输出0 0 //寻找1 lowerPos = lower_bound(a, a + 10, 1); upperPos = upper_bound(a, a + 10, 1); printf("%d %d\n", lowerPos - a, upperPos - a);//输出0 1 //寻找3 lowerPos = lower_bound(a, a + 10, 3); upperPos = upper_bound(a, a + 10, 3); printf("%d %d\n", lowerPos - a, upperPos - a);//输出3 6 //寻找4 lowerPos = lower_bound(a, a + 10, 4); upperPos = upper_bound(a, a + 10, 4); printf("%d %d\n", lowerPos - a, upperPos - a);//输出6 6 //寻找6 lowerPos = lower_bound(a, a + 10, 6); upperPos = upper_bound(a, a + 10, 6); printf("%d %d\n", lowerPos - a, upperPos - a);//输出10 10 return 0; }
显然,如果只想获得欲查元素的下标,就可以不使用临时指针,而直接令返回值减去数组首地址即可。这里补充一条知识点,指针 - 指针 = 两指针之间的元素个数
示例如下:
#include<stdio.h> #include<algorithm> using namespace std; int main() { int a[10] = { 1,2,2,3,3,3,5,5,5,5 }; //寻找3 printf("%d %d\n", lower_bound(a, a + 10, 3) - a, upper_bound(a, a + 10, 3) - a);//输出3 6 return 0; }
以上笔记出自:https://blog.csdn.net/weixin_57544072/article/details/121383654
C++ 类 & 对象
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; // 高度 // 成员函数声明 double get(void); void set( double len, double bre, double hei ); }; // 成员函数定义 double Box::get(void) { return length * breadth * height; } void Box::set( double len, double bre, double hei) { length = len; breadth = bre; height = hei; } int main( ) { Box Box1; // 声明 Box1,类型为 Box Box Box2; // 声明 Box2,类型为 Box Box Box3; // 声明 Box3,类型为 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; // box 3 详述 Box3.set(16.0, 8.0, 12.0); volume = Box3.get(); cout << "Box3 的体积:" << volume <<endl; return 0; }
Box1 的体积:210 Box2 的体积:1560 Box3 的体积:1536
需要注意的是,私有的成员和受保护的成员不能使用直接成员访问运算符 (.) 来直接访问。我们将在后续介绍如何访问私有成员和受保护的成员。
C++ 类成员函数
类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。
让我们看看之前定义的类 Box,现在我们要使用成员函数来访问类的成员,而不是直接访问这些类的成员:
class Box { public: double length; // 长度 double breadth; // 宽度 double height; // 高度 double getVolume(void);// 返回体积 };
成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义。在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。所以您可以按照如下方式定义 getVolume() 函数:
class Box { public: double length; // 长度 double breadth; // 宽度 double height; // 高度 double getVolume(void) { return length * breadth * height; } };
您也可以在类的外部使用范围解析运算符 :: 定义该函数,如下所示:
double Box::getVolume(void) { return length * breadth * height; }
在这里,需要强调一点,在 :: 运算符之前必须使用类名。调用成员函数是在对象上使用点运算符(.),这样它就能操作与该对象相关的数据,如下所示:
Box myBox; // 创建一个对象 myBox.getVolume(); // 调用该对象的成员函数
下面请看实例:
#include <iostream> using namespace std; class Box { public: double length; // 长度 double breadth; // 宽度 double height; // 高度 // 成员函数声明 double getVolume(void); void setLength( double len ); void setBreadth( double bre ); void setHeight( double hei ); }; // 成员函数定义 double Box::getVolume(void) { return length * breadth * height; } void Box::setLength( double len ) { length = len; } void Box::setBreadth( double bre ) { breadth = bre; } void Box::setHeight( double hei ) { height = hei; } // 程序的主函数 int main( ) { Box Box1; // 声明 Box1,类型为 Box Box Box2; // 声明 Box2,类型为 Box double volume = 0.0; // 用于存储体积 // box 1 详述 Box1.setLength(6.0); Box1.setBreadth(7.0); Box1.setHeight(5.0); // box 2 详述 Box2.setLength(12.0); Box2.setBreadth(13.0); Box2.setHeight(10.0); // box 1 的体积 volume = Box1.getVolume(); cout << "Box1 的体积:" << volume <<endl; // box 2 的体积 volume = Box2.getVolume(); cout << "Box2 的体积:" << volume <<endl; return 0; }
Box1 的体积: 210 Box2 的体积: 1560
C++ 内联函数
C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。
对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。
如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符。
在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符。
下面是一个实例,使用内联函数来返回两个数中的最大值:
#include <iostream> using namespace std; inline int Max(int x, int y) { return (x > y)? x : y; } // 程序的主函数 int main( ) { cout << "Max (20,10): " << Max(20,10) << endl; cout << "Max (0,200): " << Max(0,200) << endl; cout << "Max (100,1010): " << Max(100,1010) << endl; return 0; }
Max (20,10): 20 Max (0,200): 200 Max (100,1010): 1010
注意:
引入内联函数的目的是为了解决程序中函数调用的效率问题,这么说吧,程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才被替代。这其实就是个空间代价换时间的i节省。所以内联函数一般都是1-5行的小函数。在使用内联函数时要留神:
- 1.在内联函数内不允许使用循环语句和开关语句;
- 2.内联函数的定义必须出现在内联函数第一次调用之前;
- 3.类结构中所在的类说明内部定义的函数是内联函数。
重载运算符和重载函数
C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。
重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。
当您调用一个重载函数或重载运算符时,编译器通过把您所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为重载决策。
C++ 中的函数重载
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。您不能仅通过返回类型的不同来重载函数。
下面的实例中,同名函数 print() 被用于输出不同的数据类型:
#include <iostream> using namespace std; class printData { public: void print(int i) { cout << "整数为: " << i << endl; } void print(double f) { cout << "浮点数为: " << f << endl; } void print(char c[]) { cout << "字符串为: " << c << endl; } }; int main(void) { printData pd; // 输出整数 pd.print(5); // 输出浮点数 pd.print(500.263); // 输出字符串 char c[] = "Hello C++"; pd.print(c); return 0; }
整数为: 5 浮点数为: 500.263 字符串为: Hello C++
C++ 类访问修饰符
数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。类成员的访问限制是通过在类主体内部对各个区域标记 public、private、protected 来指定的。关键字 public、private、protected 称为访问修饰符。
一个类可以有多个 public、protected 或 private 标记区域。每个标记区域在下一个标记区域开始之前或者在遇到类主体结束右括号之前都是有效的。成员和类的默认访问修饰符是 private。
class Base { public: // 公有成员 protected: // 受保护成员 private: // 私有成员 };
公有(public)成员
公有成员在程序中类的外部是可访问的。您可以不使用任何成员函数来设置和获取公有变量的值,如下所示:
#include <iostream> using namespace std; class Line { public: double length; void setLength( double len ); double getLength( void ); }; // 成员函数定义 double Line::getLength(void) { return length ; } void Line::setLength( double len ) { length = len; } // 程序的主函数 int main( ) { Line line; // 设置长度 line.setLength(6.0); cout << "Length of line : " << line.getLength() <<endl; // 不使用成员函数设置长度 line.length = 10.0; // OK: 因为 length 是公有的 cout << "Length of line : " << line.length <<endl; return 0; }
Length of line : 6 Length of line : 10
私有(private)成员
私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。
默认情况下,类的所有成员都是私有的。例如在下面的类中,width 是一个私有成员,这意味着,如果您没有使用任何访问修饰符,类的成员将被假定为私有成员:
class Box { double width; public: double length; void setWidth( double wid ); double getWidth( void ); };
实际操作中,我们一般会在私有区域定义数据,在公有区域定义相关的函数,以便在类的外部也可以调用这些函数,如下所示:
#include <iostream> using namespace std; class Box { public: double length; void setWidth( double wid ); double getWidth( void ); private: double width; }; // 成员函数定义 double Box::getWidth(void) { return width ; } void Box::setWidth( double wid ) { width = wid; } // 程序的主函数 int main( ) { Box box; // 不使用成员函数设置长度 box.length = 10.0; // OK: 因为 length 是公有的 cout << "Length of box : " << box.length <<endl; // 不使用成员函数设置宽度 // box.width = 10.0; // Error: 因为 width 是私有的 box.setWidth(10.0); // 使用成员函数设置宽度 cout << "Width of box : " << box.getWidth() <<endl; return 0; }
Length of box : 10 Width of box : 10
protected(受保护)成员
protected(受保护)成员变量或函数与私有成员十分相似,但有一点不同,protected(受保护)成员在派生类(即子类)中是可访问的。
随着时间推移,您将学习到派生类和继承的知识。现在您可以看到下面的实例中,我们从父类 Box 派生了一个子类 smallBox。
下面的实例与前面的实例类似,在这里 width 成员可被派生类 smallBox 的任何成员函数访问。
#include <iostream> using namespace std; class Box { protected: double width; }; class SmallBox:Box // SmallBox 是派生类 { public: void setSmallWidth( double wid ); double getSmallWidth( void ); }; // 子类的成员函数 double SmallBox::getSmallWidth(void) { return width ; } void SmallBox::setSmallWidth( double wid ) { width = wid; } // 程序的主函数 int main( ) { SmallBox box; // 使用成员函数设置宽度 box.setSmallWidth(5.0); cout << "Width of box : "<< box.getSmallWidth() << endl; return 0; }
Width of box : 5
继承中的特点
有public, protected, private三种继承方式,它们相应地改变了基类成员的访问属性。
1.public 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:public, protected, private
2.protected 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:protected, protected, private
3.private 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:private, private, private
但无论哪种继承方式,上面两点都没有改变:
1.private 成员只能被本类成员(类内)和友元访问,不能被派生类访问;
2.protected 成员可以被派生类访问。
public 继承
#include<iostream> #include<assert.h> using namespace std; class A{ public: int a; A(){ a1 = 1; a2 = 2; a3 = 3; a = 4; } void fun(){ cout << a << endl; //正确 cout << a1 << endl; //正确 cout << a2 << endl; //正确 cout << a3 << endl; //正确 } public: int a1; protected: int a2; private: int a3; }; class B : public A{ public: int a; B(int i){ A(); a = i; } void fun(){ cout << a << endl; //正确,public成员 cout << a1 << endl; //正确,基类的public成员,在派生类中仍是public成员。 cout << a2 << endl; //正确,基类的protected成员,在派生类中仍是protected可以被派生类访问。 cout << a3 << endl; //错误,基类的private成员不能被派生类访问。 } }; int main(){ B b(10); cout << b.a << endl; cout << b.a1 << endl; //正确 cout << b.a2 << endl; //错误,类外不能访问protected成员 cout << b.a3 << endl; //错误,类外不能访问private成员 system("pause"); return 0; }
protected 继承
#include<iostream> #include<assert.h> using namespace std; class A{ public: int a; A(){ a1 = 1; a2 = 2; a3 = 3; a = 4; } void fun(){ cout << a << endl; //正确 cout << a1 << endl; //正确 cout << a2 << endl; //正确 cout << a3 << endl; //正确 } public: int a1; protected: int a2; private: int a3; }; class B : protected A{ public: int a; B(int i){ A(); a = i; } void fun(){ cout << a << endl; //正确,public成员。 cout << a1 << endl; //正确,基类的public成员,在派生类中变成了protected,可以被派生类访问。 cout << a2 << endl; //正确,基类的protected成员,在派生类中还是protected,可以被派生类访问。 cout << a3 << endl; //错误,基类的private成员不能被派生类访问。 } }; int main(){ B b(10); cout << b.a << endl; //正确。public成员 cout << b.a1 << endl; //错误,protected成员不能在类外访问。 cout << b.a2 << endl; //错误,protected成员不能在类外访问。 cout << b.a3 << endl; //错误,private成员不能在类外访问。 system("pause"); return 0; }
private 继承
#include<iostream> #include<assert.h> using namespace std; class A{ public: int a; A(){ a1 = 1; a2 = 2; a3 = 3; a = 4; } void fun(){ cout << a << endl; //正确 cout << a1 << endl; //正确 cout << a2 << endl; //正确 cout << a3 << endl; //正确 } public: int a1; protected: int a2; private: int a3; }; class B : private A{ public: int a; B(int i){ A(); a = i; } void fun(){ cout << a << endl; //正确,public成员。 cout << a1 << endl; //正确,基类public成员,在派生类中变成了private,可以被派生类访问。 cout << a2 << endl; //正确,基类的protected成员,在派生类中变成了private,可以被派生类访问。 cout << a3 << endl; //错误,基类的private成员不能被派生类访问。 } }; int main(){ B b(10); cout << b.a << endl; //正确。public成员 cout << b.a1 << endl; //错误,private成员不能在类外访问。 cout << b.a2 << endl; //错误, private成员不能在类外访问。 cout << b.a3 << endl; //错误,private成员不能在类外访问。 system("pause"); return 0; }
C++ 类构造函数 & 析构函数
类的构造函数
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。
下面的实例有助于更好地理解构造函数的概念:
实例:(建议读者自己敲一遍代码以便熟悉理解)
#include <iostream> using namespace std; class Line { public: void setLength( double len ); double getLength( void ); Line(); // 这是构造函数 private: double length; }; // 成员函数定义,包括构造函数 Line::Line(void) { cout << "Object is being created" << endl; } void Line::setLength( double len ) { length = len; } double Line::getLength( void ) { return length; } // 程序的主函数 int main( ) { Line line; // 设置长度 line.setLength(6.0); cout << "Length of line : " << line.getLength() <<endl; return 0; }
Object is being created Length of line : 6
带参数的构造函数
默认的构造函数没有任何参数,但如果需要,构造函数也可以带有参数。这样在创建对象时就会给对象赋初始值,如下面的例子所示:
#include <iostream> using namespace std; class Line { public: void setLength( double len ); double getLength( void ); Line(double len); // 这是构造函数 private: double length; }; // 成员函数定义,包括构造函数 Line::Line( double len) { cout << "Object is being created, length = " << len << endl; length = len; } void Line::setLength( double len ) { length = len; } double Line::getLength( void ) { return length; } // 程序的主函数 int main( ) { Line line(10.0); // 获取默认设置的长度 cout << "Length of line : " << line.getLength() <<endl; // 再次设置长度 line.setLength(6.0); cout << "Length of line : " << line.getLength() <<endl; return 0; }
Object is being created, length = 10 Length of line : 10 Length of line : 6
使用初始化列表来初始化字段
使用初始化列表来初始化字段:
Line::Line( double len): length(len) { cout << "Object is being created, length = " << len << endl; }
上面的语法等同于如下语法:
Line::Line( double len) { length = len; cout << "Object is being created, length = " << len << endl; }
假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化,同理地,您可以使用上面的语法,只需要在不同的字段使用逗号进行分隔,如下所示:
C::C( double a, double b, double c): X(a), Y(b), Z(c) { .... }
类的析构函数
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
下面的实例有助于更好地理解析构函数的概念:
#include <iostream> using namespace std; class Line { public: void setLength( double len ); double getLength( void ); Line(); // 这是构造函数声明 ~Line(); // 这是析构函数声明 private: double length; }; // 成员函数定义,包括构造函数 Line::Line(void) { cout << "Object is being created" << endl; } Line::~Line(void) { cout << "Object is being deleted" << endl; } void Line::setLength( double len ) { length = len; } double Line::getLength( void ) { return length; } // 程序的主函数 int main( ) { Line line; // 设置长度 line.setLength(6.0); cout << "Length of line : " << line.getLength() <<endl; return 0; }
Object is being created Length of line : 6 Object is being deleted
C++ 拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:
通过使用另一个同类型的对象来初始化新创建的对象。
复制对象把它作为参数传递给函数。
复制对象,并从函数返回这个对象。
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。拷贝构造函数的最常见形式如下:
classname (const classname &obj) { // 构造函数的主体 }
在这里,obj 是一个对象引用,该对象是用于初始化另一个对象的。
#include <iostream> using namespace std; class Line { public: int getLength( void ); Line( int len ); // 简单的构造函数 Line( const Line &obj); // 拷贝构造函数 ~Line(); // 析构函数 private: int *ptr; }; // 成员函数定义,包括构造函数 Line::Line(int len) { cout << "调用构造函数" << endl; // 为指针分配内存 ptr = new int; *ptr = len; } Line::Line(const Line &obj) { cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl; ptr = new int; *ptr = *obj.ptr; // 拷贝值 } Line::~Line(void) { cout << "释放内存" << endl; delete ptr; } int Line::getLength( void ) { return *ptr; } void display(Line obj) { cout << "line 大小 : " << obj.getLength() <<endl; } // 程序的主函数 int main( ) { Line line(10); display(line); return 0; }
调用构造函数 调用拷贝构造函数并为指针 ptr 分配内存 line 大小 : 10 释放内存 释放内存
下面的实例对上面的实例稍作修改,通过使用已有的同类型的对象来初始化新创建的对象:
#include <iostream> using namespace std; class Line { public: int getLength( void ); Line( int len ); // 简单的构造函数 Line( const Line &obj); // 拷贝构造函数 ~Line(); // 析构函数 private: int *ptr; }; // 成员函数定义,包括构造函数 Line::Line(int len) { cout << "调用构造函数" << endl; // 为指针分配内存 ptr = new int; *ptr = len; } Line::Line(const Line &obj) { cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl; ptr = new int; *ptr = *obj.ptr; // 拷贝值 } Line::~Line(void) { cout << "释放内存" << endl; delete ptr; } int Line::getLength( void ) { return *ptr; } void display(Line obj) { cout << "line 大小 : " << obj.getLength() <<endl; } // 程序的主函数 int main( ) { Line line1(10); Line line2 = line1; // 这里也调用了拷贝构造函数 display(line1); display(line2); return 0; }
调用构造函数 调用拷贝构造函数并为指针 ptr 分配内存 调用拷贝构造函数并为指针 ptr 分配内存 line 大小 : 10 释放内存 调用拷贝构造函数并为指针 ptr 分配内存 line 大小 : 10 释放内存 释放内存 释放内存
C++ 友元函数
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend,如下所示:
class Box { double width; public: double length; friend void printWidth( Box box ); void setWidth( double wid ); };
声明类 ClassTwo 的所有成员函数作为类 ClassOne 的友元,需要在类 ClassOne 的定义中放置如下声明:
friend class ClassTwo;
请看下面的程序:
#include <iostream> using namespace std; class Box { double width; public: friend void printWidth( Box box ); void setWidth( double wid ); }; // 成员函数定义 void Box::setWidth( double wid ) { width = wid; } // 请注意:printWidth() 不是任何类的成员函数 void printWidth( Box box ) { /* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */ cout << "Width of box : " << box.width <<endl; } // 程序的主函数 int main( ) { Box box; // 使用成员函数设置宽度 box.setWidth(10.0); // 使用友元函数输出宽度 printWidth( box ); return 0; }
Width of box : 10
下面演示友元类的使用:
#include <iostream> using namespace std; class Box { double width; public: friend void printWidth(Box box); friend class BigBox; void setWidth(double wid); }; class BigBox { public : void Print(int width, Box box) { // BigBox是Box的友元类,它可以直接访问Box类的任何成员 box.setWidth(width); cout << "Width of box : " << box.width << endl; } }; // 成员函数定义 void Box::setWidth(double wid) { width = wid; } // 请注意:printWidth() 不是任何类的成员函数 void printWidth(Box box) { /* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */ cout << "Width of box : " << box.width << endl; } // 程序的主函数 int main() { Box box; BigBox big; // 使用成员函数设置宽度 box.setWidth(10.0); // 使用友元函数输出宽度 printWidth(box); // 使用友元类中的方法设置宽度 big.Print(20, box); getchar(); return 0; }
C++ this 指针
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。
下面的实例有助于更好地理解 this 指针的概念:
#include <iostream> using namespace std; class Box { public: // 构造函数定义 Box(double l=2.0, double b=2.0, double h=2.0) { cout <<"Constructor called." << endl; length = l; breadth = b; height = h; } double Volume() { return length * breadth * height; } int compare(Box box) { return this->Volume() > box.Volume(); } private: double length; // Length of a box double breadth; // Breadth of a box double height; // Height of a box }; int main(void) { Box Box1(3.3, 1.2, 1.5); // Declare box1 Box Box2(8.5, 6.0, 2.0); // Declare box2 if(Box1.compare(Box2)) { cout << "Box2 is smaller than Box1" <<endl; } else { cout << "Box2 is equal to or larger than Box1" <<endl; } return 0; }
Constructor called. Constructor called. Box2 is equal to or larger than Box1
C++ 指向类的指针
一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 ->,就像访问指向结构的指针一样。与所有的指针一样,您必须在使用指针之前,对指针进行初始化。
下面的实例有助于更好地理解指向类的指针的概念:
#include <iostream> using namespace std; class Box { public: // 构造函数定义 Box(double l=2.0, double b=2.0, double h=2.0) { cout <<"Constructor called." << endl; length = l; breadth = b; height = h; } double Volume() { return length * breadth * height; } private: double length; // Length of a box double breadth; // Breadth of a box double height; // Height of a box }; int main(void) { Box Box1(3.3, 1.2, 1.5); // Declare box1 Box Box2(8.5, 6.0, 2.0); // Declare box2 Box *ptrBox; // Declare pointer to a class. // 保存第一个对象的地址 ptrBox = &Box1; // 现在尝试使用成员访问运算符来访问成员 cout << "Volume of Box1: " << ptrBox->Volume() << endl; // 保存第二个对象的地址 ptrBox = &Box2; // 现在尝试使用成员访问运算符来访问成员 cout << "Volume of Box2: " << ptrBox->Volume() << endl; return 0; }
Constructor called. Constructor called. Volume of Box1: 5.94 Volume of Box2: 102
C++ 类的静态成员
我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化,如下面的实例所示。
下面的实例有助于更好地理解静态成员数据的概念:
#include<bits/stdc++.h> using namespace std; class box { public: static int objectcount; box(double l,double b,double h) { length=l; breadth=b; height=h; objectcount++; } double volume() { return length*breadth*height; } private: double length; double breadth; double height; }; int box::objectcount=0; int main() { box box1(3.3,1.2,1.5); box box2(8.5,6.0,2.0); cout<<"Total of objectcount:"<<box::objectcount<<endl; return 0; }
静态成员函数
如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。
静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。
静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。
静态成员函数与普通成员函数的区别:
- 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
- 普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针。
#include <iostream> using namespace std; class Box { public: static int objectCount; // 构造函数定义 Box(double l=2.0, double b=2.0, double h=2.0) { cout <<"Constructor called." << endl; length = l; breadth = b; height = h; // 每次创建对象时增加 1 objectCount++; } double Volume() { return length * breadth * height; } static int getCount() { return objectCount; } private: double length; // 长度 double breadth; // 宽度 double height; // 高度 }; // 初始化类 Box 的静态成员 int Box::objectCount = 0; int main(void) { // 在创建对象之前输出对象的总数 cout << "Inital Stage Count: " << Box::getCount() << endl; Box Box1(3.3, 1.2, 1.5); // 声明 box1 Box Box2(8.5, 6.0, 2.0); // 声明 box2 // 在创建对象之后输出对象的总数 cout << "Final Stage Count: " << Box::getCount() << endl; return 0; }
Inital Stage Count: 0 Constructor called. Constructor called. Final Stage Count: 2