string类
- string类初始化的方式大概有以下几种:
string str1;
string str2 = "hello str2";
string str3("hello str3");
string str4(5, 'B');
string str5[3] = {"Xiaomi", "BYD", "XPeng"};
string str6 = str5[2];
str1 = "nihao str1";
- 通过
size()
或 length()
函数可以获取字符串的长度:
cout << str1.size() << endl;
cout << str1.length() << endl;
cout << str4.size() << endl;
cout << str5[0].size() << endl;
- 两种访问字符的方式:通过
[]
运算符或 at()
方法来访问字符串中的某个字符。at()
具有边界检查,如果访问越界会抛出异常,而 []
不会做边界检查。[]
导致的越界会产生未定义行为,越界后会访问到其他地址,系统不会报错;at()
只要一越界就要抛出错误,程序终止。一个string占32个字节。
cout << str2[7] << endl;
cout << str2.at(7) << endl;
cout << str5[0][2] << endl;
cout << str5[0].at(2) << endl;
- 拼接字符串:
#include <stdio.h>
#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
string str1 = "成长是永远";
string str2 = "离别是空悬";
cout << str1 + str2 << endl;
return 0;
}
- 比较字符串:可以直接使用
==
, !=
, <
, >
, <=
, >=
来比较两个字符串的字典顺序。对应ASCII码值大的就大,逻辑表达式为真返回1,为假返回0.
string str1 = "abc";
string str2 = "xyz";
cout << (str1 <= str2) << endl;
- 子字符串提取:
substr()
函数用于提取子字符串。它接受两个参数:起始索引和长度(可选)
string str1 = "abcdefghijkl";
string str2 = str1.substr(1, 10);
cout << str2 << endl;
- 插入删除和替换操作:
string str1 = "hello";
str1.insert(5, " world");
string str1 = "hello world";
str1.erase(5, 3);
string str1 = "hello world";
str1.replace(5, 6, "C++");
- 查找字符串:
find()
函数可以查找子字符串或字符的第一个出现位置,如果没有找到,find()
会返回 std::string::npos
,这里因为我开头用了using namespace std;
,所以不需要再用std::
声明
string str1 = "hello world";
if (str1.find("world") != string::npos)
{
cout << "I find it" << endl;
}
else
{
cout << "I can't find it" << endl;
}
- C++风格字符串与C风格字符串转换:
string str1 = "hello world";
const char *str2 = str1.c_str();
cout << str2 << endl;
const char *str1 = "hello world";
string str2(str1);
cout << str2 << endl;
- 内存管理:string会自动处理内存分配和释放,不需要手动管理内存。string通过深拷贝的方式处理赋值操作,确保每个字符串对象都有自己独立的内存。
- 什么是深拷贝?什么是浅拷贝?
- 深拷贝:深拷贝是在拷贝的时候将内容申请内存,重新拷贝一份,放到内存中,指针指向这个新拷贝的部分,这样就不会出现析构的时候重复释放的问题了。
- 浅拷贝:浅拷贝就是增加了一个指向相同堆区的指针,这将导致在析构的时候会重复释放。默认的拷贝构造和运算符重载都是浅拷贝。
- 什么是析构:析构(Destructor)是一个特殊的成员函数,用于在对象生命周期结束时清理资源并执行必要的清理工作。析构函数的主要目的包括释放对象在其生命周期内分配的动态内存、关闭文件句柄、释放网络连接等。简单理解就是清理资源的成员函数。
函数
- 函数可以设置默认参数,如果调用时不传入参数则使用默认参数值:
void nameCountry(string name = "China")
{
cout << name << endl;
}
- 指针传递和引用传递:
- 指针传递在C语言和C++中都能使用,指针传递是通过传递变量的地址来间接修改变量的值。
- 引用传递,只能在C++中使用。如下第一种函数就是引用传递。即直接操作传入的变量的引用,传递给函数时,函数内部对引用变量的修改会直接反映到原始变量上。
void swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
- 函数的重载:是一种允许在同一个作用域内定义多个具有相同名字但参数列表不同的函数的特性。
- 重载规则:
- 参数的类型不同
- 参数的个数不同
- 参数的顺序不同
- 不允许重载的情况:
- 仅有返回值不同(函数的返回类型不能作为函数重载的依据)
- 函数使用默认参数时,可能会导致重载冲突(默认参数的存在导致二义性)
const
修饰符可以参与函数重载。- 数组和指针的重载:数组类型会退化为指针。(在 C语言 中,数组作为函数参数时也会 退化为指针。)
int funcPlus(int x, double y)
{
cout << "这是funcPlus1" << endl;
return x + (int)y;
}
int funcPlus(double x, double y)
{
cout << "这是funcPlus2" << endl;
return (int)(x + y);
}
int funcPlus(int x)
{
cout << "这是funcPlus3" << endl;
return x * x;
}
int funcPlus(double x, int y)
{
cout << "这是funcPlus4" << endl;
return (int)(x + y);
}
int func(int x, int y = 10)
{
cout << "这是funcPlus5" << endl;
return x + (int)y;
}
int func(int x)
{
cout << "这是funcPlus6" << endl;
return x;
}
void func(int &x);
void func(const int &x);
void func(int arr[])
{
cout << "arr[]" << endl;
cout << arr[0] << endl;
}
void func(int *arr)
{
cout << "*arr" << endl;
cout << arr[0] << endl;
}
类与多态性
- OOP:面向对象编程
- 类在声明时是不申请内存的,只有在实例化时才会初始化(分配)内存
- C++支持类的多继承;Java不支持类的多继承(但支持接口的多继承)
class Person
{
private:
int age;
public:
string name;
Person(int a, string name);
~Person();
};
Person::Person(int age, string name)
{
cout << "构造函数执行:" << endl;
this->age = age;
this->name = name;
}
Person::~Person()
{
cout << "析构函数执行:" << endl;
}
- 析构函数的作用:主要作用是在对象的生命周期结束时执行清理工作。
- 释放动态分配的内存:运行过程中通过
new
或 malloc
等函数动态分配了内存,析构函数负责调用 delete
或 free
来释放这些内存。 - 释放其他系统资源:对象可能持有除内存以外的系统资源,比如文件句柄、网络连接、数据库连接等。
- 执行其他清理工作:析构函数可以用于任何需要在对象销毁时执行的操作,比如保存数据、记录日志等。
- 析构函数不接受参数,不能重载。
- 如果创建了多个实例,析构函数就会执行多次。
- 类的访问控制:
public
:公开的成员可以在类外部访问。private
:私有的成员只能在类内部访问,不能直接在类外部使用,但可以通过类方法间接调用。protected
:受保护的成员可以在类内部和派生类(如子类)中访问。
- 类的静态成员:
- 静态成员变量:属于类本身,而不是某个对象。它在所有对象中共享。
- 静态成员函数:只能访问静态成员变量,不依赖于对象。静态成员可以通过类名直接调用,如:
Person::getCount()
其中Person
是类名,getCount()
是静态成员函数
- 继承:
- 继承 允许一个类(派生类)从另一个类(基类)继承属性和行为。
- C++支持单继承和多继承。
- 派生类可以直接使用基类的公有和受保护成员。
class Animals
{
public:
void eat()
{
cout << "eating" << endl;
}
};
class Dog : public Animals
{
public:
void bark()
{
cout << "wow wow" << endl;
}
};
- 友元函数:友元函数不是类的成员函数,但可以访问类的私有和受保护成员。它是在类外定义的,但具有特殊权限。
class Person
{
private:
int age;
public:
Person(int age)
{
this->age = age;
}
friend void getAge(Person);
};
void getAge(Person per)
{
cout << "岁数是:" <<per.age << endl;
}
- 多态:
- 多态性允许同一个函数或方法在不同的对象上表现出不同的行为。
- C++ 中的多态性通过虚函数、继承和指针或引用实现。
- 基本概念:
- 编译时多态(静态多态):通过函数重载和运算符重载实现,编译时决定。
- 运行时多态(动态多态):通过继承和虚函数实现,运行时决定。
- 虚函数:
sound()
是基类 Animal
中的虚函数,Dog
和 Cat
类分别重写了这个函数。- 在运行时,
animalPtr
指向不同的派生类对象时,会根据实际对象的类型来调用相应的 sound()
方法,这就是 运行时多态。
- 纯虚函数与抽象类:
- 纯虚函数 是在基类中声明但没有实现的虚函数。
- 通过定义纯虚函数,基类变为 抽象类,它不能被实例化,只能作为派生类的基类使用。
- 纯虚函数与抽象类的代码如下:
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void sound() = 0;
};
class Dog : public Animal
{
public:
void sound() override
{
cout << "Dog barks" << endl;
}
};
class Cat : public Animal
{
public:
void sound() override
{
cout << "Cat meow" << endl;
}
};
int main(int argc, char const *argv[])
{
Animal *animalptr;
Dog dog;
Cat cat;
animalptr = &dog;
animalptr->sound();
animalptr = &cat;
animalptr->sound();
return 0;
}
- 多态与析构函数:
- 通过基类指针删除派生类对象时,如果基类的析构函数不是
virtual
,则可能会发生 内存泄漏。 - 因为如果基类指针的析构函数不是虚函数,那么通过基类指针删除派生类对象时,调用的是基类的析构函数,导致派生类的资源没有被正确释放
- 代码如下:
#include <iostream>
using namespace std;
class Base
{
public:
virtual ~Base()
{
cout << "基类 析构函数执行" << endl;
}
};
class Derived : public Base
{
public:
~Derived() override
{
cout << "派生类 析构函数执行" << endl;
}
};
int main()
{
Base *basePtr = new Derived;
delete basePtr;
return 0;
}
- 对象切片:
- 如果通过 值传递(而非指针或引用)传递对象,会发生 对象切片,即派生类对象的派生部分会被切掉,变成一个基类对象。
#include <iostream>
using namespace std;
class Base
{
public:
virtual void show()
{
cout << "Base show" << endl;
}
};
class Derived : public Base
{
public:
void show() override
{
cout << "Derived show" << endl;
}
};
void display(Base &obj)
{
obj.show();
}
int main()
{
Derived d;
display(d);
return 0;
}
- 对象传递最好是传递指针或者引用,直接进行值传递会导致对象切片:
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void sound()
{
cout << "Animal sound" << endl;
}
};
class Dog : public Animal
{
public:
void sound() override
{
cout << "Dog barks" << endl;
}
};
int main(int argc, char const *argv[])
{
Dog dog;
dog.sound();
Animal *basePtr = &dog;
basePtr->sound();
Animal &baseRef = dog;
baseRef.sound();
Animal baseObj = dog;
baseObj.sound();
return 0;
}