内存分区模型
C++程序在执行时,将内存大方向划分为4个区域
- 代码区:存放函数体的二进制代码,由操作系统进行管理的
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
内存四区意义:
- 不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程
程序运行前
运行前代码放在磁盘里,运行之后各种变量才会进入内存里,也就是栈区和堆区
程序运行后
栈区:
- 由编译器自动分配释放,存放函数的参数值(形参),局部变量等(出了代码块,局部变量释放)
- 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
堆区:
- 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
- 在C++中主要利用new在堆区开辟内存
new操作符
- C++中利用new操作符在堆区开辟数据堆区开辟的数据,由程序员手动开辟,手动释放
- 释放利用操作符 delete
- 语法:new 数据类型
- 利用new创建的数据,会返回该数据对应的类型的指针
引用
- 给变量起别名(是同一块地址)(本质是指针常量)指针常量和常量指针-CSDN博客
- 语法:数据类型 &别名=原名
int a=10;
int &b=a;
b=20;
cout<<a<<endl; //20
引用注意事项
- 引用必须初始化,引用一旦初始化后,就不可以更改了
- 引用本质是指针常量,指针常量本质上一个常量,指针用来说明常量的类型,表示该常量是一个指针类型的常量。
- 在指针常量中,指针自身的值是一个常量,不可改变,始终指向同一个地址。在定义的同时必须初始化。
引用做函数参数
void func(int &a,int &b)//起别名,是同一块地址
{
int temp=a;
a=b;
b=temp;
}
int main()
{
int a=10,b=20;
func(a,b);
cout<<a<<" "<<b;
}
- 总结:通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单
引用做函数返回值
int& text01()
{
int a=10;//局部变量存放在四区的栈区,离开代码块销毁
return a;//以引用的形式返回,相当于返回这块地址,已被销毁
}
int& text02()
{
static int a = 10;//静态变量,存放在全局区,全局区上的数据在程序结束后系统释放
}
int main()
{
int &ref = text01();
cout<<"ref="<<ref<<endl;//ref=10 ref=0
cout<<"ref="<<ref<<endl;//第一次编译器保留,后正常销毁
int &ref2 = text02();
cout<<"ref="<<ref<<endl;
cout<<"ref="<<ref<<endl;
//引用的函数调用可以作为左值
rext02()=1000;
cout<<"ref2="<<ref2<<endl;//此时test02(),ref,a都变成同等地位的20的一个代号
cout<<"ref2="<<ref2<<endl;
}
- ref2 是 test02()的别名 test02()是a的别名 所以 ref 2=test02()=a
- 因此当test02()被重新赋值时,ref2的值也会改变!
引用的本质
本质:引用的本质在c++内部实现是一个指针常量
- 指针需要开辟新的空间,但是引用不需要
- 引用不支持取地址操作,取的地址是原数据的地址
- 而指针取的是指针的地址
常量引用
作用:用来修饰形参,防止误操作
- 形参改变实参这种情况只可能发生在地址传递或引用传递之中。
- 常量引用=常量指针常量
void showValue(const int &val)
{
//val=1000;
}
int main()
{
int a=10;
//int &ref=10;//引用必须引一块合法的内存空间,10是常量(字面量)
const int & ref=10;//加上const修饰,编译器将代码修改
//相当于int temp=10; const int &ref=temp;(给10一块地址)
//ref=20; 加入const后变为只读,不可以修改
showValue(a);
}
函数提高
函数默认参数
- 在C++中,函数的形参列表中的形参是可以有默认值的。
- 语法:返回值类型 函数名 (参数=默认值)
- 默认参数必须在参数列表的末尾
- 如果函数声明有默认参数,函数实现就不能有默认参数
int func(int a,int b=20,int c=30)
//如果我们自己传入数据,就用自己的数据,如果没有,那么用默认值
{
return a+b+c;
}
int main()
{
cout<<func(10,30)<<endl;
}
函数占位参数
- C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
- 语法:返回值类型 函数名(数据类型){}
- 在现阶段函数的占位参数存在意义不大,但是后面的课程中会用到该技术
void func(int a,int)//这个int就是函数占位,目前用不到
{
cout<<"hello world"<<endl;
}
void func2(int a,int =10)//可以有默认参数
{
}
int main()
{
func(10,10);
func2(10);
}
函数重载
函数重载概括
作用:函数名可以相同,提高复用性
函数重载满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同 或者 个数不同 或者 顺序不同
- 注意:函数的返回值不可以作为函数重载的条件
void func(){
cout<<"调用1 "<<endl;
}
void func(int a){
cout<<"调用2 "<<a<<endl;
}
void func(double a){
cout<<"调用3 "<<a<<endl;
}
void func(double a,int a);
void func(int a,double a);
int main(){
func();//调用1
func(2);//调用2
func(2.5);//调用3
return 0;
}
函数重载注意事项
- 引用作为重载条件
- 函数重载碰到函数默认参数
void func(int &a){
cout<<1<<endl;
}
void func(const int &a){
cout<<2<<endl;
}
void func2(int a,int b){
cout<<3<<endl;
}
void func2(int a){
cout<<4<<endl;
}
void func2(int a,int b=10){
cout<<5<<endl;
}
int main(){
int a=10;
func(a);//将变量a传过去,只能用1接收
func(10);//将常量a传过去,相当于常数引用,只能用2接收
func2(10);//可以传给4,也可以传给5,存在二义性
}
- 本质是需要让计算机可以区分两个函数
类和对象
- C++面向对象的三大特性为: 封装、继承、多态
- C++认为万事万物都皆为对象对象上有其属性和行为
例如:
- 人可以作为对象,属性有姓名、年龄、身高、体重....行为有走、跑、跳、吃饭、唱歌..
- 车也可以作为对象,属性有轮胎、方向盘、车灯…,行为有载人、放音乐、放空调...
- 具有相同性质的对象,我们可以抽象称为类,人属于人类,车属于车类
封装
封装是C++面向对象三大特性之一
封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
封装意义一:
- 在设计类的时候,属性和行为写在一起,表现事物
- 语法:class 类名{ 访问权限:属性 /行为 };
//设计一个圆类,求圆的周长
//公式:2*PI*半径
//class代表设计一个类,后跟类名称
class Circle{
//访问权限:公共权限
public:
//属性:半径
int m_r;
//行为:获取圆的周长
double calculateZC(){
return 2*3.14*m_r;
}
};
int main(){
Circle cl;//实例化(通过一个类,创建一个对象)
cl.m_r=10;//给属性赋值
cout<<"圆的周长"<<cl.calculateZC()<<endl;//调用类的行为
}
class Student{
public:
string m_Name;
int m_id;
public://可不写
void showStudent(){
cout<<"姓名: "<<m_Name<<"学号: "<<m_id<<endl;
}
//给名字赋值
void setName(string name){
m_Name=name;
}
//给学号赋值
void setId(int id){
m_id=id;
}
};
int main(){
//创建一个具体学生实例化对象
Student s1;//给s1对象 进行属性赋值操作
// sl.m Name ="张三";
// sl.m Id = 1;
s1.setName("张三");
s1.setId(1);
//显示学生信息
s1.showStudent();
Student s2;
s2.m_Name ="李四";
s2.m_id = 2;
s2.showStudent();
}
- 类中的属性和行为 我们统一称为 成员
- 属性 成员属性 成员变量
- 行为 成员函数 成员方法
- 尽量叫方法,类中的函数全部叫作方法,方法是面向对象的东西
封装意义二:
- 类在设计时,可以把属性和行为放在不同的权限下,加以控制访问权限有三种:
- 1. public公共权限 类外类内都可以访问
- 2.protected 保护权限 类外不可以访问 继承的对象可以访问保护内容
- 3. private私有权限 类外不可以访问 继承的对象也不能访问内容
struct和class区别
在C++中 struct和class区别在于默认的访问权限不同
- struct 默认权限为 公共 public
- class 默认权限为 私有 private
- 注:c++中的strust结构体获得类的大部分功能,区别在与默认权限
封装读写权限操作
class Person
{
// 私有类内传给公共再传给类外
public://公有
//设置姓名
void setName(string name)
{
m_Name=name;
}
//获取姓名
string getName()
{
return m_Name;
}
//获取年龄
int get_Age()
{
return m_Age;
}
//设置偶像
void setIdol(string idol)
{
m_Idol=idol;
}
private:
string m_Name;//姓名 可读可写
int m_Age=18;//年龄 只读
string m_Idol;//偶像 只写
};
int main(){
Person p;
//姓名设置
p.setName("张三");
cout<<"姓名是"<<p.getName()<<endl;
cout<<"年龄是"<<p.get_Age();
p.setIdol("周深");
}
- 可以自己控制读写权限
- 对于写可以检测数据有效性
- 为了让类的使用者只能通过设计者提供的接口去修改类的属性
//设置年龄(0~150)
void setAge(int age)
{
if(age<0||age>150)
{
cout<<"输入有误,赋值失败"<<endl;
return;
}
m_Age=age;
}
练习案例
- 设计立方体类(Cube)
- 求出立方体的面积和体积
- 分别用全局函数和成员函数判断两个立方体是否相等
//立方体类设计
//创建立方体类
//设计属性
//设计行为 获取立方体的面积和体积
//分别利用全局函数和成员函数 判断两个立方体是否相等
class Cube{
public:
//设置长宽高
void setS(int l,int w,int h){
m_W=w;
m_H=h;
m_L=l;
}
//获取长宽高
int getH(){
return m_H;
}
int getL(){
return m_L;
}
int getW(){
return m_W;
}
//获取立方体面积
int calculateS(){
return 2*m_L*m_W+2*m_H*m_W+2*m_L*m_H;
}
//获取体积
int calculateV(){
return m_L*m_W*m_H;
}
//类内比较两个立方体
//比较外界立方体和类内立方体
bool isSameByClass(Cube &c){
if(m_L==c.getH()&&m_W==c.getL()&&m_H==c.getW())
return true;
}
private:
int m_L;
int m_W;
int m_H;
};
bool isSame(Cube &cl,Cube &c2){//引用是为了节省空间,仅判断不需要实参
if(cl.getH()==c2.getH()&&cl.getL()==c2.getL()&&cl.getW()==c2.getW())
{
return true;
}
}
int main()
{
Cube cl;
cl.setS(10,10,10);
cout<<cl.calculateS()<<endl;
cout<<cl.calculateV()<<endl;
Cube c2;
c2.setS(10,10,10);
if(isSame(cl,c2)){
cout<<"相等"<<endl;
}
if(cl.isSameByClass(c2)){
cout<<"相等"<<endl;
}
}
#include<iostream>
using namespace std;
//点和圆的关系
//点类
class Point {
public:
void setX(int x){
m_x = x;
}
int getX(){
return m_x;
}
void setY(int y){
m_y = y;
}
int getY(){
return m_y;
}
private:
int m_x;
int m_y;
};
//圆类
class Circle{
public:
void setR(int r){
m_r = r;
}
int getR(){
return m_r;
}
void setCenter(Point center){
m_Center = center;
}
Point getCenter(){
return m_Center;
}
private:
int m_r;
Point m_Center;//圆心(圆心和点都属于点类)
};
void isInCircle(Circle &c, Point &p){
//计算两点之间距离(平方)
int distance =
(c.getCenter().getX() - p.getX())*(c.getCenter().getX() - p.getX()) +
(c.getCenter().getY() - p.getY())*(c.getCenter().getY() - p.getY());
//计算半径(平方)
int rDistance = c.getR()*c.getR();
//判断关系
if (distance == rDistance)
{
cout << "点在圆上" << endl;
}
else if (distance > rDistance) {
cout << "点在圆外" << endl;
}
else {
cout << "点在圆内" << endl;
}
}
int main() {
//创建圆
Circle c;
c.setR(10);
Point center;
center.setX(10);
center.setY(0);
c.setCenter(center);
//创建点
Point p;
p.setX(10);
p.setY(10);
//判断关系
isInCircle(c, p);
}
类的分装
//Cricle.h
#pragma once
#include<iostream>
#include"point.h"
//圆类
class Circle {
public:
void setR(int r);
int getR();
void setCenter(Point center);
Point getCenter();
private:
int m_r;
//在类中,可以让另一个类作为成员
Point m_Center;//圆心(圆心和点都属于点类)
};
//cricle.cpp
#include"Circle.h"
//圆类
void Circle::setR(int r) {
m_r = r;
}
int Circle::getR() {
return m_r;
}
void Circle::setCenter(Point center) {
m_Center = center;
}
Point Circle::getCenter() {
return m_Center;
}
对象的初始化和清理
- 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全
- C++中的面向对象来源于生活,每个对象也都会有初始设置以及 对象销毁前的清理数据的设置。
构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题
- 一个对象或者变量没有初始状态,对其使用后果是未知
- 同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
c++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供。编译器提供的构造函数和析构函数是空实现。
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
- 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法: ~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号 ~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
class Person {
public:
//1.构造函数 进行初始化操作
Person()
{
cout << "构造" << endl;
}
//析构函数 进行清理的操作
~Person()
{
cout << "析构" << endl;
}
};
//构造和析构必须有,不写,编译器自己提供空的实现
void test01() {
Person p;//在栈上的数据,text01执行完,释放这个对象
}
int main(){
test01();//打印构造
return 0;//打印析构
}
构造函数的分类及调用
两种分类方法:
- 按参数分为: 有参构造和无参构造
- 按类型分为: 普通构造和拷贝构造
三种调用方式:
- 括号法
- 显示法
- 隐式转换法
class Person {
public:
//无参构造(默认构造)
Person() {
cout << "无参构造" << endl;
}
//有参构造
Person(int a,int b) {
age = a;
i = b;
cout << "有参构造" << endl;
}
//拷贝构造函数,构造函数的作用就是初始化属性,
//那么拷贝构造相当于传进来一个有属性的类,
//来初始化我这个类的属性
Person(const Person &p){//引用防止递归调用,const防止修改
//将传入的人身上的属性,拷贝到我身上
cout << "拷贝构造" << endl;
age = p.age;
}
~Person()
{
cout << "无参析构" << endl;
}
int age;
int i;
};
void test01()
{
//括号法
Person p1;//无参(默认)构造函数调用
Person p2(10,2);
Person p3(p2);
cout << "p2 " << p2.age <<p2.i<< endl;
cout << "p3 " << p3.age <<p3.i<< endl;
//显示法
Person p2 = Person(10,2);
Person p3 = Person(p2);
//右边是匿名对象,左边是对象的名
//特点:当前行执行结束后,系统会立即回收(析构)掉匿名对象
Person(10, 2);//匿名对象
//隐形转换法
Person p4 = { 10,2 };//相当于Person p4=Person(10,2);
Person p5 = p4;
}
int main(){
test01();
}
注意事项:
- 调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明
- 不要利用拷贝构造函数,初始化匿名对象 编译器认为是对象的声明,导致重定义
相当于显示法的 Person p3 =Person(p3); - 拷贝构造虽然是引用,但是对象的地址并不相同
拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
class Person {
public:
Person() {
cout << "无参构造" << endl;
}
Person(int age){
cout << "有参构造" << endl;
m_age = age;
}
Person(const Person &p){
cout << "拷贝构造" << endl;
m_age = p.m_age;
}
~Person(){
cout << "无参析构" << endl;
}
int m_age;
};
void test01() {
Person p1(20);
Person p2(p1);
}
void doWork(Person p_1){
}
//值传递的方式给函数参数传值
void text02(){
Person p;
doWork(p);//相当于隐形转换Person p_1=p; 会进行拷贝构造
}
//值传递返回局部对象
Person dowork2() {
Person p1;
cout << (int*)&p1<<endl;
return p1;//进行了拷贝构造和无参析构(复制一份的同时销毁掉)
}
void test03() {
Person p = dowork2();
cout << (int*)&p << endl;
}
int main()
{
//test01();
//text02();
//test03();
}
构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数(对属性进行值拷贝)
构造函数调用规则如下:
- 如果用户定义有参构造函数,c++不在提供默认无参构造(Person p失效),但是会提供默认拷贝构造(可以实现拷贝功能)
- 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
- 拷贝>有参>无参 (有前就不提供后面)
深拷贝和浅拷贝
- 浅拷贝:简单的赋值拷贝操作(编译器默认使用)
- 深拷贝:在堆区重新申请空间,进行拷贝操作
class Person{
public:
Person(){
cout << "无参构造" << endl;
}
Person(int age,int height) {
m_age = age;
m_height = new int(height);
cout << "有参构造" << endl;
}
~Person() {//将堆区开辟数据做释放操作
if (m_height != NULL) {
delete m_height;
m_height = NULL;
}
cout << "无参析构" << endl;
}
Person(const Person &p) {
cout << "拷贝构造函数" << endl;
m_age = p.m_age;
//m_height = p.m_height; 搬一起默认实现的就是这行代码
//深拷贝操作
m_height = new int(*p.m_height);
}
int m_age;
int *m_height;
};
void text01() {
Person p1(18,160);
cout << "p1年龄为" << p1.m_age <<"身高为"<<*p1.m_height<< endl;
Person p2(p1);
cout << "p2的年龄为" << p2.m_age << endl;
}
int main() {
text01();
}
- 假设需要将数据建立的堆区,那需要用指针的形式创造 (因为堆区的数据可以手动释放,在函数体外也可以使用)
- 那如果拷贝构造的话需要使用new创建数据(利用new创建的数据,会返回该数据对应的类型的指针)
- 编译器默认提供拷贝构造函数,进行浅拷贝操作,将p1和p2的数据完全一致的拷贝过来,导致对象p2中的指针变量和p1的指针一样,指向和p1相同的堆区内存空间(即两个指针指向同一块堆区地址)
m_height = p.m_height; - 析构的时候会发生同一块堆区反复释放
- 所以需要手动拷贝构造,m_height = new int(*p.m_height); 即深拷贝
STL
STL初识
概念
- STL(Standard Template Library,标准模板库)
- STL 从广义上分为: 容器(container) 算法(algorithm)选代器(iterator)
- 容器和算法之间通过迭代器进行无缝连接。
- STL 几乎所有的代码都采用了模板类或者模板函数
STL六大组件
STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
- 1.容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据
- 2.算法:各种常用的算法,如sort、find、copy、for_each等
- 3.迭代器:扮演了容器与算法之间的胶合剂。
- 4. 仿函数:行为类似函数,可作为算法的某种策略。
- 5.适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
- 6.空间配置器:负责空间的配置与管理,
STL中容器、算法、迭代器
容器:置物之所也
STL容器就是将运用最广泛的一些数据结构实现出来
常用的数据结构:数组, 链表,树, 栈, 队列, 集合, 映射表 等
- 这些容器分为序列式容器和关联式容器两种:
- 序列式容器:强调值的排序,序列式容器中的每个元素均有固定的位置。
- 关联式容器:二叉树结构,各元素之间没有严格的物理上的顺序关系
**算法:**问题之解法也
有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法(Algorithms)
- 算法分为:质变算法和非质变算法。
- 质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等
- 非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等
**迭代器:**容器和算法之间粘合剂
提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
每个容器都有自己专属的迭代器
迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针
迭代器种类:
常用的为双向迭代器和随机访问迭代器
容器算法迭代器初识
vector存放内置数据类型
- 容器:
vector
- 算法:
for_each
- 迭代器:
vector<int>::iterator
#include<bits/stdc++.h>
using namespace std;
void myPrint(int value) {
cout << value << endl;
}
//vector容器存放内置数据结构
void test01() {
//创建一个vector容器,数组
vector<int> v;
//向容器中插入数据
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
//通过迭代器访问容器中的数据
//获取容器的开始迭代器,指向第一个元素
vector<int>::iterator itBegin = v.begin();
//获取容器的结束迭代器,指向最后一个元素的下一位置
vector<int>::iterator itEnd = v.end();
//第一种遍历方式:几乎不用
while(itBegin != itEnd) {
cout <<*itBegin<< endl;
itBegin++;
}
//第二种遍历方式
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << endl;
}
//第三种遍历方式,利用STL提供的遍历算法
for_each(v.begin(), v.end(), myPrint);
//第四种遍历方式,利用数组下标访问元素
for (int i = 0; i < v.size(); i++) {
cout << v[i] << endl;
}
}
int main() {
test01();
return 0;
}
vector存放自定义数据类型
#include<bits/stdc++.h>
using namespace std;
//vector容器中存放自定义数据类型
class Person {
public:
Person(string name, int age) {
this->name = name;
this->age = age;
}
string name;
int age;
};
void text01() {
vector<Person> v;
v.push_back(Person("Alice", 25));
v.push_back(Person("Bob", 30));
v.push_back(Person("Charlie", 35));
//使用下标遍历vector容器
for (int i = 0; i < v.size(); i++) {
cout << "姓名:" << v[i].name << " 年龄:" << v[i].age << endl;
}
//使用迭代器遍历vector容器
for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
cout << "姓名:" << it->name << " 年龄:" << it->age << endl;
}
}
int main() {
text01();
};
vector容器嵌套容器
#include <bits/stdc++.h>
using namespace std;
//容器嵌套容器
void test01() {
vector<vector<int>> v;
//创建小容器
vector<int> v1;
vector<int> v2;
vector<int> v3;
vector<int> v4;
//向小容器中添加元素
for (int i = 0; i < 4; i++) {
v1.push_back(i);
v2.push_back(i + 1);
v3.push_back(i + 2);
v4.push_back(i + 3);
}
//向大容器中添加小容器
v.push_back(v1);
v.push_back(v2);
v.push_back(v3);
v.push_back(v4);
//利用下标访问元素输出大容器
for (int i = 0; i < v.size(); i++) {
for (int j = 0; j < v[i].size(); j++) {
cout << v[i][j] << " ";
}
cout << endl;
}
//利用迭代器访问元素输出大容器
for (vector<vector<int>>::iterator it = v.begin(); it != v.end(); it++) {
for (vector<int>::iterator it2 = (*it).begin(); it2 != (*it).end(); it2++) {
cout << *it2 << " ";
}
cout << endl;
}
}
int main() {
test01();
return 0;
}
String基本概念
本质:
- string是C++风格的字符串,而string本质上是一个类
string和char * 区别:
- char * 是一个指针。
- string是一个类,类内部封装了char*,管理这个字符串,是一个char*型的容器。
- string和char类型数组比起来末尾多了一个\0。
特点:
- string 类内部封装了很多成员方法
- 例如:查找find,拷贝copy,删除delete 替换replace,插入insert
- string管理char*所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责
string构造函数
构造函数原型:
string();
//创建一个空的字符串 例如: string str;string(const char* s);
//使用字符串s初始化string(const string& str);
//使用一个string对象初始化另一个string对象string(int n, char c);
//使用n个字符c初始化#include <bits/stdc++.h> using namespace std; void test01() { //这是字符串对象,编译器会在堆上分配内存,并将其地址赋值给s1 string s1; //这是字符串字面值常量,编译器会将其直接内联到程序中,不会产生新的内存分配 const char* s2 = "Hello World"; //这是字符数组,编译器会在栈上分配内存,并将其地址赋值给s3 char s3[] = "Hello World"; string s4(s2); //利用字符串初始化 cout << s4 << endl; string s5(s3); //利用字符数组初始化 cout << s5 << endl; string s6(10, 'a'); //利用指定长度和字符初始化 cout << s6 << endl; string s7(s4); //利用字符串拷贝初始化 cout << s7 << endl; string s8(s4, 3, 5); //利用字符串和范围初始化 cout << s8 << endl;//lo Wo } int main() { test01(); return 0; }
arr[0] 的赋值操作不是在尝试修改原始的字符串字面值 “aaa”,而是将 arr[0] 的指针从原来指向 “aaa” 的地址改为指向 “baa” 的地址。这是合法的操作,因为它只涉及指针本身的改变,而不涉及尝试修改常量数据。
总结来说,arr[0] = “baa”; 可以进行赋值,因为它仅仅修改了指针的指向,而不是尝试修改字符串字面值本身的内容。
String赋值操作
功能描述:
- 给string字符串进行赋值
#include <bits/stdc++.h>
using namespace std;
void test01() {
string str1="Hello World";//把字符串常量(类型const char*)赋值给str1
cout<<str1<<endl;
string str2=str1;//把str1的副本赋值给str2
cout<<str2<<endl;
//string str3 = 'a';//不能在声明的时候直接用单个字符对字符串进行赋值
string str3;
str3 = 'a';//可以用单个字符对字符串进行赋值
cout<<str3<<endl;
string str4;
str4.assign("Hello C++");//用assign函数赋值
cout<<str4<<endl;
string str5;
str5.assign("hello", 3);//前3个字符赋值hel
cout<<str5<<endl;
string str6;
str6.assign(str1, 3, 5);//从str1下标3开始复制5个字符lo wo
cout<<str6<<endl;
}
int main() {
test01();
return 0;
}
string的赋值方式很多,
=
这种方式是比较实用的
string字符串拼接
功能描述:
- 实现在字符串末尾拼接字符串
#include<bits/stdc++.h>
using namespace std;
//字符串拼接
void test01()
{
string str1 = "我";
str1 += "爱玩游戏";
cout << "str1 = " << str1 << endl;
str1 += ':';
cout << "str1 = " << str1 << endl;
string str2 = "LOL DNF";
str1 += str2;
cout << "str1 = " << str1 << endl;
string str3 = "I";
str3.append(" love ");
str3.append("game abcde", 4);
str3.append(str2);
str3.append(str2, 4, 3); // 从下标4位置开始 ,截取3个字符,拼接到字符串末尾
cout << "str3 = " << str3 << endl;
}
int main() {
test01();
return 0;
}
string增删查改
功能描述:
- 查找:查找指定字符串是否存在
- 替换:在指定的位置替换字符串
#include <bits/stdc++.h>
using namespace std;
//查找
void test01() {
string str1 = "abcdefgcd";
//返回cd的位置(首次出现的下标),如果没有找到,返回-1
cout << str1.find("cd");
//返回cd的位置(最后出现的下标),如果没有找到,返回-1
cout << str1.rfind("cd");
}
//替换
void test02() {
string str1 = "abcdefgcd";
//替换cd为ef
str1.replace(str1.find("cd"), 2, "ef");
cout << str1;
//替换下标1-3位为1111
str1.replace(1, 3, "1111");//a1111efgcd
cout << str1;
}
//插入
void test03() {
string str1 = "abcdefgcd";
//插入1111
str1.insert(1, "1111");//a1111bcdefgcd
cout << str1<<endl;
//在cd后面插入ef
str1.insert(str1.find("cd") + 2, "ef");
cout << str1<<endl;
}
//删除
void test04() {
string str1 = "abcdefgcd";
//删除ab
str1.erase(0, 2);//从0开始删除2个字符,即ab
cout << str1;
//删除cd
str1.erase(str1.find("cd"), 2);
cout << str1;
}
int main() {
//test01();
//test02();
//test03();
//test04();
return 0;
}
- find查找是从左往后,rfind从右往左
- find找到字符串后返回查找的第一个字符位置,找不到返回-1
- replace在替换时,要指定从哪个位置起,多少个字符,替换成什么样的字符串
- 插入和删除的起始下标都是从0开始
string字符串比较
功能描述:
- 字符串之间的比较
比较方式:
- 字符串比较是按字符的ASCII码进行对比
- = 返回 0
- > 返回 1
- < 返回 -1
//字符串比较
void test01()
{
string s1 = "hello";
string s2 = "aello";
int ret = s1.compare(s2);
if (ret == 0) {
cout << "s1 等于 s2" << endl;
}
else if (ret > 0)
{
cout << "s1 大于 s2" << endl;
}
else
{
cout << "s1 小于 s2" << endl;
}
}
int main() {
test01();
return 0;
}
字符串对比主要是用于比较两个字符串是否相等,判断谁大谁小的意义并不是很大
string字符存取
string中单个字符存取方式有两种
char& operator[](int n);
//通过[]方式取字符char& at(int n);
//通过at方法获取字符
#include<bits/stdc++.h>
using namespace std;
void test01()
{
string str = "hello world";
for (int i = 0; i < str.size(); i++)
{
cout << str[i] << " ";
}
cout << endl;
for (int i = 0; i < str.size(); i++)
{
cout << str.at(i) << " ";
}
cout << endl;
//字符修改
str[0] = 'x';
str.at(1) = 'x';
cout << str << endl;
}
int main() {
test01();
return 0;
}
string子串
功能描述:
- 从字符串中获取想要的子串
#include<bits/stdc++.h>
using namespace std;
//子串
void test01()
{
string str = "abcdefg";
//从第1个字符开始,截取3个字符
string subStr = str.substr(1, 3);
cout << "subStr = " << subStr << endl;
string email = "hello@sina.com";
int pos = email.find("@");
//从0开始,截取到@的位置
string username = email.substr(0, pos);
cout << "username: " << username << endl;
//从@的位置开始,截取到结尾
string domain = email.substr(pos + 1);
cout << "domain: " << domain << endl;
}
int main() {
test01();
return 0;
}
vector容器
vector基本概念
功能:
- vector数据结构和数组非常相似,也称为单端数组
vector与普通数组区别:
- 不同之处在于数组是静态空间,而vector可以动态扩展
动态扩展:
- 并不是在原空间之后续接新空间,而是找更大的内存空间,然后将原数据拷贝新空间,释放原空间
- vector容器的迭代器是支持随机访问的迭代器
vector构造函数
功能描述:
- 创建vector容器
函数原型:
vector<T> v;
//采用模板实现类实现,默认构造函数vector(v.begin(), v.end());
//将v[begin(), end())区间中的元素拷贝给本身。vector(n, elem);
//构造函数将n个elem拷贝给本身。vector(const vector &vec);
//拷贝构造函数。
#include<bits/stdc++.h>
using namespace std;
void printVector(vector<int> v)
{
for (int i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
}
void test01()
{
vector<int> v1; //无参构造
for (int i = 0; i < 10; i++)
{
v1.push_back(i);
}
printVector(v1);
vector<int> v2(v1.begin(), v1.end());//将v1的元素拷贝到v2
printVector(v2);
vector<int> v3(10, 100);//构造函数,将10个100填充到v3
printVector(v3);
vector<int> v4(v3);//拷贝构造
printVector(v4);
}
int main() {
test01();
return 0;
}
vector赋值操作
功能描述:
- 给vector容器进行赋值
函数原型:
-
vector& operator=(const vector &vec);
//重载等号操作符 -
assign(beg, end);
//将[beg, end)区间中的数据拷贝赋值给本身。 -
assign(n, elem);
//将n个elem拷贝赋值给本身。
#include<bits/stdc++.h>
using namespace std;
void print(vector<int> v)
{
for(int i=0;i<v.size();i++)
cout<<v[i]<<" ";
cout<<endl;
}
void test01(){
//直接赋值
vector<int> v0={1,2,3,4,5};
print(v0);
//输入赋值
vector<int> v6;
for (int i = 0; i < 10; i++){
int x;
cin >> x;
v6.push_back(x);
}
print(v6);
vector<int> v1;
for(int i=0;i<10;i++)
v1.push_back(i);
print(v1);
//拷贝构造函数
vector<int> v2=v1;
print(v2);
vector<int> v3(v1);
print(v3);
//assign函数
vector<int> v4;
v4.assign(v1.begin(),v1.end());
print(v4);
//n个element的构造函数
vector<int> v5(10,1);
print(v5);
}
int main() {
test01();
return 0;
}
vector容量和大小
功能描述:
- 对vector容器的容量和大小操作
函数原型:
-
empty();
//判断容器是否为空 -
capacity();
//容器的容量 -
size();
//返回容器中元素的个数 -
resize(int num);
//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。-
//如果容器变短,则末尾超出容器长度的元素被删除。
-
-
resize(int num, elem);
//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。-
//如果容器变短,则末尾超出容器长度的元素被删除
-
#include<bits/stdc++.h>
using namespace std;
void print(vector<int> v) {
for (int i = 0; i < v.size(); i++) {
cout << v[i] << " ";
}
cout << endl;
}
void test01() {
vector<int> v1;
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
print(v1);
if (v1.empty()) {
cout << "v1为空"<<endl;
}
else {
cout << "v1不为空"<<endl;
cout << "v1的容量为:" << v1.capacity() << endl;
cout << "v1的大小为:" << v1.size() << endl;
}
//重新指定大小
v1.resize(15);//若指定大小大于当前大小则会扩大
//v1.resize(15, 100);
print(v1);//默认填充0,若指定参数则填充指定值
v1.resize(5);//若小于当前大小则会缩小
print(v1);
}
int main() {
test01();
return 0;
}
总结:
- 判断是否为空 --- empty
- 返回元素个数 --- size
- 返回容器容量 --- capacity
- 重新指定大小 --- resize
vector插入和删除
功能描述:
- 对vector容器进行插入、删除操作
函数原型:
push_back(ele);
//尾部插入元素elepop_back();
//删除最后一个元素insert(const_iterator pos, ele);
//迭代器指向位置pos插入元素eleinsert(const_iterator pos, int count,ele);
//迭代器指向位置pos插入count个元素eleerase(const_iterator pos);
//删除迭代器指向的元素erase(const_iterator start, const_iterator end);
//删除迭代器从start到end之间的元素clear();
//删除容器中所有元素