CPPLearn基础1
CPPLearn1
new创建一维数组
int *p = new int[10];//new 10个元素的整型数组
delete[] p;
//注意动态创建的数组不能使用sizeof函数,永远都是8
//加上了std::nothrow参数后,创建失败会返回nullptr,不会终止程序
int *p = new(std::nothrow) int[10000000];
if(p == nullptr){
std::cout<<"创建失败"<<std::endl;
}else{
std::cout<<"创建成功"<<std::endl;
}
//不要使用delete来释放不是new创建的动态内存
//不要对同一个内存块释放两次
一维数组的排序qsort
void qsort(void* base,size_t num,size_t width,int(__cdecl*compare)(const void*,const void*));
使用快速排序对数组排序参数: 1 待排序数组,排序之后的结果仍放在这个数组中
2 数组中待排序元素数量
3 各元素的占用空间大小(单位为字节)sizeof(数组数据类型)
4 指向函数的指针,用于确定排序的顺序(需要用户自定义一个比较函数)回调函数size_t :在64位系统中是8字节无符号整型,
typedef unsigned long long size_t
#include <iostream>
using namespace std;
int compare(const void* a, const void* b) {
return *(int*)a - *(int*)b;//从小到大排序
//reutrn *(int*)b - *(int*)a;//从大到小排序
}
int main() {
int a[8] = { 3,4,5,6,7,2,0,9 };
qsort(a,sizeof(a)/sizeof(int),sizeof(int), compare);
for (int ii = 0; ii < sizeof(a) / sizeof(int); ii++) {
cout << "a[" << ii << "] = " << a[ii] << endl;
}
return 0;
}
一维数组的查找(折半查找)
先对数组进行从小到大排序。然后查找
//在arr数组中查找key
int search(int arr[], int len, int key) {
int low = 0, high = len - 1, mid;
while (low <= high) {
mid = (high + low) / 2;
if (arr[mid] == key) return mid;
else if (arr[mid] > key) high = mid - 1;
else low = mid + 1;
}
return -1;
}
C风格字符串
如果字符型数组的末尾包含空字符
\0
,也就是0,那么该数组的内容也就是一个字符串因为字符串需要使用0来结尾,所以声明字符数组的时候,要预留多一个字节用来存放0。
#include <iostream>
using namespace std;
int main() {
string a = "xyz";
cout << (int)a[0] << endl;
cout << (int)a[1] << endl;
cout << (int)a[2] << endl;
cout << (int)a[3] << endl;
return 0;
}
/*
120
121
122
0
*/
-
清空字符串:
memset(name,0,sizeof(name))
,将数组中的所有元素置为0 -
字符串赋值或者复制:
char *strcpy(char* dest, const char* src)
。将参数src字符串拷贝给dest所指向的地址。复制完字符串后,会在dest的后面追加0。如果dest所指向的内存不够大,会越界。
-
字符串赋值或者复制2:
char* strncpy(char* dest, const char* src, const size_t n);
。将src数组中的前n个字符复制到dest中。
如果src的长度大于等于n,那么截取src的前面n个字符,不会在dest后追加0。
如果src的长度小于n,那么拷贝完了字符串后,在dest后追加0,直到n个。
参数dest指向的内存不够大的话,会导致数组越界。
-
获取字符串长度:
size_t strlen(const char* str)
,计算字符串的有效长度,不包含0。 -
字符串拼接:
char* strcat(char* dest, const char* src);
,将src字符串拼接到dest的尾部。 -
字符串拼接2:
char* strncat(char* dest, const char* src, size_t n);
将src字符串的前n个字符串拼接到dest的尾部。
- 字符串比较:
int strcmp(const char* str1, const char* str2);
比较str1和str2字符串的大小,相等返回0,str1 > str2返回1,否则返回-1
- 字符串比较2:
int strncmp(const char* str1, const char* str2, size_t n);
比较str1和str2字符串前n个字符的大小,相等返回0,str1 > str2返回1,否则返回-1
- 字符串查找:
char* strchr(const char* s, const int c);
将返回指向字符串s中第一次出现c的位置的指针。如果找不到,返回空指针。
- 字符串查找2:
char* strrchr(const char* s, const int c);
将返回指向字符串s中最后一次出现c的位置的指针。如果找不到,返回空指针。
- 字符串查找3:
char* strstr(const char* str, const char* substr);
检索子串在字符串中首次出现的位置。返回str字符串中第一次出现substr字符串的地址,如果没有检索到子 串,那么就返回空指针。
注意:在VS中使用这些字符串操作函数会报错,在文件的最上面添加#define _CRT_SECURE_NO_WARNINGS
二维数组遍历
#include <iostream>
using namespace std;
int main() {
int kl[2][3] = { {1,2,3},{4,6,8} };
for (int ii = 0; ii < 2; ii++) {// 行数
for (int jj = 0; jj < 3; jj++) { // 列数
cout << kl[ii][jj] << "\t";
}
cout << endl;
}
return 0;
}
- 二维数组也是可以使用
memset
函数进行清空为0的。
复制二维数组
void *memcpy(void* dest, const void *src, size_t n);
。可以把数组中的全部的元素复制到另一个相同大小的数组(没有说是几维数组)。(只是适用于C++的基本数据类型)
#include <iostream>
using namespace std;
int main() {
int kl[2][3] = { {1,2,3},{4,6,8} };
int kl1[6];
memcpy(kl1,kl,sizeof(kl));
for (int ii = 0; ii < 2; ii++) {
for (int jj = 0; jj < 3; jj++) {
cout << kl[ii][jj] << "\t";
}
cout << endl;
}
for (int ii = 0; ii < 6; ii++) {
cout << kl1[ii] << "\t";
}
return 0;
}
/*
1 2 3
4 6 8
1 2 3 4 6 8
*/
二维数组做函数参数
//行指针
int kl[2][3] = { {1,2,3},{4,6,8} };
int(*p)[3] = kl;
//二位数组做参数
void func(int (*p)[3], int len);
void func2(int p[][3], int len);
//遍历二维数组
void func2(int p[][3], int len){
for(int ii = 0;ii < len; ii++){
for(int jj = 0; jj < 3; jj++){
cout << "p[" << ii << "][" << jj << "] = " << p[ii][jj] <<" ";
}
cout<<endl;
}
}
结构体
struct St_girl{
unsigned int age;
char name[21];
char sex;
};
// 结构体占用的内存不一定是所有的变量占用字节之和,还和字节对齐有关
// #pragma pack(字节数) 来修改字节对齐。缺省值是8
#pragma pack(1)
struct St_girl stgirl;
memset(&stgirl, 0, sizeof(St_girl));//清空结构体
bzero(&stgirl,sizeof(St_girl));//清空结构体
St_girl* post = &stgirl;
cout << stgirl->sex << endl;//访问就结构体中的变量
cout << (*stgirl).sex << endl;
// 动态分配内存
St_girl* stgirl2 = new St_girl({0});
St_girl* stgirl3 = new St_girl({23,"西瓜",1});
结构体中的指针
#include <iostream>
using namespace std;
struct St_t
{
int a;
int* p;
};
int main() {
St_t stt;
stt.p = new int[100];
//清空结构体
//memset(&stt, 0, sizeof(St_t)); //错误的,会将地址清空为0,会出现内存泄漏
stt.a = 0;
memset(stt.p, 0, 100 * sizeof(int));
return 0;
}
简单链表
#include <iostream>
using namespace std;
struct DataLink {
int no;
string name;
DataLink* next;
};
int main() {
DataLink* head = nullptr;//头节点
DataLink* tail = nullptr;//尾节点
DataLink* temp = nullptr;//临时指针
//分配一个节点
temp = new DataLink({1,"西施",nullptr});
head = tail = temp;
//创建新的节点
temp = new DataLink({ 2,"貂蝉",nullptr });
tail->next = temp; // 将上一个节点的next指针指向新的节点
tail = temp;
//创建新的节点
temp = new DataLink({ 3,"王昭君",nullptr });
tail->next = temp; // 将上一个节点的next指针指向新的节点
tail = temp;
//遍历链表
temp = head;
while (temp != nullptr) {
cout << "no = " << temp->no << "name = " << temp->name << endl;
temp = temp->next; // 临时节点后移
}
//释放链表
while (head != nullptr) {
temp = head; //让临时节点指向头节点
head = head->next;// 头节点后移
delete temp; // 删除临时节点
}
return 0;
}
共用体
共用体能够存储不同的数据类型,但是在同一个时间只能够存储其中的一种数据类型
占用内存的大小是占用内存最大的成员字节对齐的结果,全部成员用同一块内存。
#include <iostream>
using namespace std;
union udata {
int a;
double b;
char c[12];
};
int main() {
udata data;
cout << "union udata 占用的内存" << sizeof(udata) << endl;
data.a = 30;
cout << data.a << endl;
cout << data.b << endl;
cout << data.c << endl;
return 0;
}
枚举
枚举的变量取值,只能够在枚举的范围之内。
可以显示的设置枚举量的值。枚举量的值可以重复。
#include <iostream>
using namespace std;
enum colors {
red = 0,
yellow = 1,
blue = 2
};
int main() {
colors cc = red;// cc的值只能够使用red、yellow、blue三中,也不可以使用0、1、2
switch (cc) {
case red:
cout << "红色" << endl;
break;
case yellow:
cout << "黄色" << endl;
break;
case blue:
cout << "绿色" << endl;
break;
default:
cout << "其他颜色" << endl;
break;
}
return 0;
}
引用
基本概念与本质
引用时已经定义的变量的别名。主要用途是用作函数的形参和返回值。
数据类型 &引用名 = 原变量名
#include <iostream>
using namespace std;
// 引用的本质:指针常量 int* const p;
// 引用int& 编译器会将他替换为int* const
int main() {
int a = 3;
// 引用必须要初始化,初始化后就不可以在改变
int& b = a;// b是a的别名,他们的值和内存单元相同,引用的变量类型必须相同
cout << "a的地址是" << &a << "a的值是" << a << endl;
cout << "b的地址是" << &b << "b的值是" << b << endl;
b = 8;
cout << "a的地址是" << &a << "a的值是" << a << endl;
cout << "b的地址是" << &b << "b的值是" << b << endl;
return 0;
}
引用做函数参数
#include <iostream>
using namespace std;
//注意不要返回局部变量引用。会引起错误
void func1(int no) { //值传递
no = 8;
cout << "值传递" << no << endl;
}
void func2(int* no) {//地址传递
*no = 8;
cout << "地址传递" << *no << endl;
}
void func3(int& no) {//引用传递
no = 18;
cout << "引用传递" << no << endl;
}
int main() {
int no = 5;
func1(no);
cout << "值传递--》" << no << endl;
func2(&no);
cout << "地址传递--》" << no << endl;
func3(no);
cout << "引用传递--》" << no << endl;
return 0;
}
const和引用
const int& a = 8; //编译器处理为 int temp = 8; const int& a = temp;
//int& b = 8; //报错,这里的8是没有地址的,int& == int* const
void func(const int& a, const string& str); // 那么这样使用的时候就可以传常量进去
// eg : func(8,"你好"); //这样使用是可以通过的。
// 这样的话,上面的函数和void func(const int a , const string str);是类似的。
引用作为函数返回值
传统的函数返回机制和值传递的类似。
函数的返回值被拷贝到一个临时的位置(寄存器或者栈),然后调用者程序在使用这个值。
double m = aqrt(36);
,sqrt函数的返回值6被拷贝到临时的位置,然后赋值给m。为了避免这种临时变量,可以返回指针或者引用。不会产生内存拷贝。
/* 错误,返回局部变量的引用,会产生也指针
int& func(){
int ii = 2;
return ii;
}
int& b = func();
*/
// 可以返回函数的引用参数,类的变量、全局变量、静态变量(stati修饰)。
int& func1(int& ra){
ra++;
return ra; //返回引用形参
}
// 返回引用的函数是被引用的变量的别名,也就是说 int& b = func(a); b就是func(a)的别名。
// 那么就可以通过func(a)来改变b的值,也就是func(a) = 3; 函数变为是可以修改的左值。
// 不想这样的话,将const 用于引用的返回类型
const int& func1(int& ra){
ra++;
return ra; //返回引用形参
}
int a = 3;
const int& b = func(a);
// 一般的默认规则
// 对于不修改实参的,一般实参是及结构较大的,我们使用const引用,
// 如果是类就用const引用,是数组就是用const指针。结构小的可以使用值传递
// 对于修改实参的,如果是内置数据类型的话使用指针,实参是数组的话使用指针,
// 实参是结构体的话使用指针或者引用,实参是类就是用引用
函数的默认参数
#include <iostream>
using namespace std;
void func(const string& message) { // const string& 的原因是减少函数使用的拷贝内容步骤
cout << "你好 , " << message << endl;
}
// 函数的默认参数
void func2(const string& message = "你是谁!!!") {
cout << "你好 , " << message << endl;
}
int main() {
func("我是你的朋友!!!");
func2(); // 缺省值
return 0;
}
- 如果函数的声明和定义是分开写的,那么在函数的声明中要书写默认参数,函数的定义中不能书写默认参数。
- 函数必须要从右到左设置默认参数,也就是说如果某个参数设置了默认参数,那么他后面的参数都要设置默认参数。
- 调用函数的时候,如果指定了某个参数的值,那么该参数前面所有的参数都必须要指定。
函数重载
函数重载是设计一系列同名的函数,让他们完成相似的工作。
名称相同,但是条件是形参的个数或者数据类型或者排列顺序不同。
#include <iostream>
using namespace std;
void swap(int& a, int& b) { // 交换两个int类型数据
int temp = a;
a = b;
b = temp;
}
void swap(string& a, string& b) { // 交换两个string类型数据
string temp = a;
a = b;
b = temp;
}
int main() {
int a = 5, b = 3;
string name1 = "tom";
string name2 = "jack";
cout << "交换前 a = " << a << "; b = " << b << endl;
swap(a, b);
cout << "交换后 a = " << a << "; b = " << b << endl;
cout << "交换前 name1 = " << name1 << "; name2 = " << name2 << endl;
swap(a, b);
cout << "交换后 name1 = " << name1 << "; name2 = " << name2 << endl;
return 0;
}
-
视需求重载各类数据类型,不要重载功能不同的函数。
-
使用函数重载的时候,数据类型不匹配的话,C++将会尝试使用类型转换与形参进行匹配,如果转换后有多个函数匹配,那么就会报错。
-
引用可以作为函数重载的条件,但是,调用函数的时候,如果实参是变量,那么编译器形参类型本身和类型的引用将被视为同一个特征。
-
如果函数重载有默认参数,调用函数时,可以回引起匹配失败问题。
-
const和返回值都不能作为函数重载的特征。
内联函数
提高程序执行的速度。减少函数执行时候地址跳转之间的时间,缺点是要占用更多的内存。
#include <iostream>
using namespace std;
inline void func(const string& message) {
cout << "message = " << message << endl;
}
int main() {
// 加入inline内联的时候,相当于
// func("123");
{
const string& message = "123";
cout << "message = " << message << endl;
}
//func("456");
{
const string& message = "456";
cout << "message = " << message << endl;
}
//func("789");
{
const string& message = "789";
cout << "message = " << message << endl;
}
return 0;
}
- 如果函数过大,编译器可能不会把他作为内联函数。
- 内联函数不能递归。
- 内联函数的声明和定义一般来说是放在一起的。代码一般来说比较简短。
类
class People {
public:
int age;
char sex;
void setage();
void getage();
void getsex();
void setsex();
};
- 类的成员可以是变量,也可以是函数。
- 类的成员变量也就叫做属性,类的成员函数也叫做方法。成员函数可以定义在类的外部。
- 创建一个类的变量叫做实例化。
- 类的成员变量和成员函数的作用域和生命周期和对象的作用域和生命周期相同。
类的访问权限
有三种访问权限:
public
、protected
、private
。
在类的内部,无论是什么访问权限,都是可以访问的。
在类的外部,只能访问public
成员,不能访问其他二种权限的成员。
结构体的缺省权限是public
,类的缺省权限是private
。
private
是隐藏类的数据和实现,而public
是向外面暴露出来的成员和数据
构造函数和析构函数
构造函数:实例化时,自动初始化工作。
析构函数:销毁对象时,自动完成清理工作。
#include <iostream>
using namespace std;
class People {
public:
int age;
char sex;
// 有参构造
People(int age, char sex) {
this->age = age;
this->sex = sex;
cout << "有参构造" << endl;
}
// 无参构造
People() {
age = 0;
sex = ' ';
cout << "无参构造" << endl;
}
// 析构函数
~People() {
cout << "析构函数" << endl;
}
};
int main() {
People people1(23, 'Y');
return 0;
}
- 如果没有提供构造和析构函数,那么编译器会提供空实现的构造函数
- 我们提供了构造和析构,那么编译器就不会提供构造函数
- 没有参数的构造函数称为默认构造函数。
- 创建匿名对象:
People()
或者People(24, 'X')
。创建完后马上又被销毁。
// 在析构函数中写释放内存
People(){
ptr = nullptr;
}
void creat(){
ptr = new int(10); // 动态分配内存
}
~People(){
if(ptr != nullptr){ // 释放动态分配的内存
delete ptr;
}
}
// 不建议在在构造函数和析构函数之中写太多的代码,可以调用成员函数。不让他们执行太复杂的功能。
拷贝构造函数
用一个已经存在的对象初始化新创建对象。
People p1(23,'X');
People p2(p1); //拷贝
People p3 = p2; //拷贝
// 拷贝构造函数,类中定义了,编译器不会提供,反之会提供浅拷贝构造函数(默认构造函数),如果有动态分配内存的
// 那么析构的时候就会出现问题。这就是深拷贝和浅拷贝。
People(const People& p){
this.name = p.name;
this.sex = p.sex;
}
深拷贝和浅拷贝
浅拷贝后指针指向同一地址,内存重复释放。
#include <iostream>
using namespace std;
class Girl {
public:
int age;
int* ptr;
Girl() {
this->age = 0;
this->ptr = nullptr;
}
Girl(int age) {
this->age = age;
this->ptr = new int(4); // 堆区创建内存,并赋值4
}
//Girl(const Girl& girl) { // 浅拷贝构造 重复释放内存,报错
// this->age = girl.age;
// this->ptr = girl.ptr;
//}
Girl(const Girl& girl) { // 深拷贝构造
this->age = girl.age;
int* temp = new int(*girl.ptr); //创建新的堆区存放拷贝的数据
// 拷贝数据其他两种方法
// *temp = *girl.ptr
// memcpy(this.ptr,girl.ptr,sizeof(int));
this->ptr = temp;
}
~Girl() {
if (ptr != nullptr) {
delete ptr;
ptr = nullptr;
}
}
};
int main() {
Girl g1(23);
Girl g2(g1);// 拷贝
return 0;
}
初始化列表
构造函数的执行可以分为:初始化阶段和计算阶段,初始化阶段先于计算阶段。
初始化阶段:初始化全部成员,计算阶段:执行构造函数中的赋值操作。
语法:类名(形参列表):成员一(值一),成员二(值二),....{.......}
class Student {
int age;
string ID;
Student(int age, const string& ID) :age(age), ID(ID) {}
Student() :age(23), ID("1234") {}
};
- 如果成员已经在初始化列表中,就不应该在构造函数中再次赋值。
- 初始化列表的括号中可以是具体的值,也可以是构造函数的形参,还可以是表达式。
- 初始化列表和赋值是有本质的区别,如果成员是类,使用初始化列表调用的是成员类的拷贝构造函数,而赋值则是先创建成员类的对象(将调用成员类的普通构造函数),然后在赋值。所以初始化列表的效率更高。
#include <iostream>
using namespace std;
class Teacher {
public:
int t_age;
Teacher() { this->t_age = 21; cout << "Teacher 无参构造" << endl; }
Teacher(int age) :t_age(age) {
cout << "Teacher 有参构造函数" << endl;
}
Teacher(const Teacher& t) {
this->t_age = t.t_age;
cout << "Teacher 拷贝构造函数" << endl;
}
~Teacher() {
cout << "Teacher 析构函数" << endl;
}
};
class Student {
public:
int age;
string ID;
Teacher t;
Student(int age, const string& ID,const Teacher& t1) :age(age), ID(ID),t(t1) {
cout << "Student 有参构造函数" << endl;
}
Student() :age(23), ID("1234") {
cout << "Student 无参构造函数" << endl;
Teacher t1;
t = t1;
}
~Student() {
cout << "Student 析构函数" << endl;
}
};
int main() {
Teacher t;
Student s(23,"123",t);
//Student s;
return 0;
}
- 当是用
Student s(23,"123",t);
代码时候结果
- 当是使用
Student s;
代码的时候
- 如果成员是常量或者引用,必须使用初始化列表,因为常量和引用只能在定义的时候被初始化。
const修饰成员函数
在类的成员函数后面加上
const
关键字,表示在函数中保证不会修改调用对象的成员变量。
#include <iostream>
using namespace std;
class Boy {
public:
int age;
string name;
Boy() :age(24), name("jack") {}
void showBoy()const;// 并不会修改成员变量的值,函数后面加上const
};
void Boy::showBoy()const {
cout << "男孩姓名:" << this->name << " ; 他的年龄是" << this->age << endl;
}
int main() {
Boy b1;
b1.showBoy();
return 0;
}
- 使用
mutable
关键字可以突破const
的限制,被mutable
修饰的变量是处于永远可变的状态。在const
修饰的函数中,mutable
修饰的变量也是可以被修改的。 - 非
const
成员函数可以调用const
成员函数和非const
成员函数。 const
对象,也就是常对象只能调用const
修饰的成员函数,不能调用非cosnt
修饰的成员函数。
类的静态成员
类的静态成员包含静态成员变量和静态成员函数。用静态成员可以将变量实现在对个对象之间的数据共享,比全局变量更加的具有安全性。
- 用
static
修饰的成员变量为静态的,表示它在程序中是共享的。谁都可以访问。 - 静态成员函数只能使用静态成员变量,并且其中没有
this
指针。 - 静态成员变量不会在创建对象的时候初始化,必须要在程序的全局区用代码清晰的初始化。
- 静态成员使用类名加范围解析运算符(::)就可以访问了,不需要创建对象。
- 把类的成员声明为静态的,就可以把他和类的对象独立开来(静态成员不属于对象)
- 私有的静态成员在类外不能被访问。
const
静态成员变量可以在定义类的时候初始化。
#include <iostream>
using namespace std;
class Boy {
public:
int age;
string name;
static int high; // 静态成员变量
Boy(const string& name, int age) :age(age), name(name) {}
static void showHigh() { cout << high << endl; } // 静态成员函数,只能使用静态成员变量
};
int Boy::high = 187; // 初始化静态成员变量,必须放在全局区
int main() {
cout << Boy::high << endl;
Boy::showHigh();
return 0;
}
友元
提供另一访问类私有成员的方案:友元全局函数,友元类,友元成员函数。
#include <iostream>
using namespace std;
class Boy; // 先定义Boy类,让Girl类可以访问
// 友元类,访问Boy类的私有成员
class Girl {
public:
// 友元类
/*void showBoyAge(const Boy& b)const {
b.showAge();
}*/
// 成员函数做友元,必须要在外部进行代码编写
void showBoyAgeByFunc(const Boy& b)const;
Girl() :age(32) {}
private:
int age;
};
class Boy {
friend void global_showAge();
//friend class Girl;
friend void Girl::showBoyAgeByFunc(const Boy& b)const;
public:
string name;
Boy() :name("西施"),age(43) {}
void showName()const { cout << name << endl; }
private:
int age;
void showAge()const { cout << age << endl; }
};
// 成员函数做友元,要放在Boy类的下面
void Girl::showBoyAgeByFunc(const Boy& b)const {
b.showAge();
}
// 全局友元函数
void global_showAge() {
Boy c;
c.showAge();
}
int main() {
Boy b;
b.showName();
global_showAge();
Girl g;
//g.showBoyAge();
g.showBoyAgeByFunc(b);
return 0;
}
运算符重载
基础知识
#include <iostream>
using namespace std;
class Student {
friend void addHigh(Student& s, float high1);
friend Student& operator+(Student& s, float high1);
friend Student& operator-(Student& s, float high1);
public:
string name;
Student() :name("西施"), age(23), high(178) {}
void printStudent() {
cout << name << " 年龄 " <<
age << " 身高 " << high << endl;
};
Student& operator*(const float b);
private:
int age;
float high;
};
// 成员函数对*的重载
Student& Student::operator*(const float b) {
this->high = this->high * b;
return *this;
}
// 友元实现+
void addHigh(Student& s, float high1) {
s.high = s.high + high1;
}
// +运算符的重载 注意形参的顺序说明 s 必须要在 high1的左边 s = 10 + s; 是编译不通过的。
Student& operator+(Student& s, float high1) {
s.high = s.high + high1;
return s;
}
// -运算符的重载
Student& operator-(Student& s, float high1) {
s.high = s.high - high1;
return s;
}
int main() {
Student s;
addHigh(s, 0.5);
s.printStudent();
s = s + 0.5;//身高长高0.5cm
s.printStudent();
s = s - 2.5;
s.printStudent();
s = s * 0.924;
s.printStudent();
return 0;
}
- 返回自定义数据类型的引用可以让多个运算符表达式串联起来。
- 重载函数参数列表中的顺序决定了操作数的位置。
- 重载函数的参数列表中至少有一个是用户自定义数据类型,防止程序员为内置数据类型重载运算符。
- 如果重载可以是全局函数和成员函数,那么优先考虑成员函数。
- 重载函数不能违背运算符原来的含义和优先级。
- 不能创建新的运算符。
=
、()
、[]
、->
只能通过成员函数进行重载,而<<
只能通过全局函数重载。
重载关系运算符
#include <iostream>
using namespace std;
class Student {
public:
Student() :name("西施"), age(23), high(178),scores(98) {}
Student(string name1,int age1,float high1,float scores1)
:name(name1), age(age1), high(high1), scores(scores1) {}
// 比较两个学生的身高
bool operator==(const Student& s);
bool operator<(const Student& s);
bool operator>(const Student& s);
bool operator!=(const Student& s);
private:
string name;
int age;
float high;
float scores;
};
bool Student::operator==(const Student& s) {
return (this->high == s.high);
}
bool Student::operator<(const Student& s) {
return (this->high < s.high);
}
bool Student::operator>(const Student& s) {
return (this->high > s.high);
}
bool Student::operator!=(const Student& s) {
return (this->high != s.high);
}
int main() {
Student s;
Student s2("李白",23,190,99);
if (s == s2) {
cout << "=====" << endl;
}
else if (s > s2) {
cout << ">>>>>" << endl;
}
else if (s < s2) {
cout << "<<<<<<" << endl;
}
if (s != s2) {
cout << "!=!=!=" << endl;
}
return 0;
}
重载左移运算符
重载
<<
运算符用于输出自定义对象的成员变量,在实际开发中很有实际价值。只能使用全局函数进行重载,如果要输出私有成员变量,可以配合友元进行使用。
#include <iostream>
using namespace std;
class Student {
friend ostream& operator<<(ostream& cout, const Student& s);
public:
Student() :name("西施"), age(23), high(178),scores(98) {}
Student(string name1,int age1,float high1,float scores1)
:name(name1), age(age1), high(high1), scores(scores1) {}
ostream& operator<<(ostream& cout) {
cout << this->name << " 年龄 "<< this->age << " 身高 "
<< this->age << " 分数 " << this->scores << endl;
return cout;
}
private:
string name;
int age;
float high;
float scores;
};
ostream& operator<<(ostream& cout,const Student& s) {
cout << s.name << " 年龄 " << s.age << " 身高 " << s.age << " 分数 " << s.scores << endl;
return cout;
}
int main() {
Student s;
// 局部变量重载
cout << s << endl;
// 成员变量重载
s << cout << endl;
return 0;
}
重载下标运算符
#include <iostream>
using namespace std;
class Student {
public:
Student() :name("西施"), age(23), high(178), scores(98), friends{"李白","华安","王华"} {}
Student(string name1,int age1,float high1,float scores1,const string* friends1)
:name(name1), age(age1), high(high1), scores(scores1) {
this->friends[0] = friends1[0];
this->friends[1] = friends1[1];
this->friends[2] = friends1[2];
}
string& operator[](int ii);
//const string& operator[](int ii)const; // 用于常对象的使用
private:
string name;
int age;
float high;
float scores;
string friends[3];
};
string& Student::operator[](int ii) {
return this->friends[ii];
}
//const string& Student::operator[](int ii)const { // 可能平常打印日志使用的是这种,更加的具有安全性。
// return this->friends[ii];
//}
int main() {
Student s;
s[1] = "jack";
cout << s[1] << endl;
return 0;
}
重载赋值运算符
#include <iostream>
using namespace std;
// 编译器自己本身会提供一个赋值运算符的重载,但是是浅拷贝,称为默认赋值函数。
// 下面的赋值运算符的重载是深拷贝。
class Student {
public:
Student() :name("西施"), age(23),ptr(new int(5)) {}
Student(string name, int age, const int& p) {
this->name = name;
this->age = age;
this->ptr = new int(p);
}
Student(const Student& s) { // 拷贝构造
this->age = s.age;
this->name = s.name;
this->ptr = new int(*s.ptr);
}
Student& operator=(const Student& s) { // 赋值运算符的重载
if (this = &g){ // 有可能出现s = s的情况
return *this;
}
if (this->ptr != nullptr) { // 清空旧有的内存
delete ptr;
age = 0;
name = "";
}
this->ptr = new int(*s.ptr); // 创建新的堆区空间
this->age = s.age;
this->name = s.name;
return *this;
}
~Student() {
if (ptr != nullptr) {
delete ptr;
ptr = nullptr;
}
}
void setName(const string& name1) {
this->name = name1;
}
void showStudent() {
cout << this->name << " 年龄是" << this->age << " ptr=" << *ptr << endl;
}
private:
string name;
int age;
int* ptr;
};
int main() {
Student s,s1;
s.showStudent();
s.setName("王昭君");
s1 = s;
s1.showStudent();
return 0;
}
重载new或malloc运算符
#include <iostream>
using namespace std;
void* operator new(size_t size) {
cout << "调用全局new函数 申请到" <<size<<"字节"<< endl;
void* ptr = malloc(size);
cout << "申请到的地址" << ptr << endl;
return ptr;
}
void operator delete(void* ptr) {
cout << "调用全局delete函数" << endl;
if (ptr == nullptr) return;
free(ptr);
}
class People {
public:
int age;
string name;
People() :age(23), name("西施") { cout << "调用无参构造函数" << endl; }
People(int age,const string& name) :age(age), name(name) { cout << "调用有参构造函数" << endl; }
~People() { cout << "调用析构函数" << endl; }
// 默认就是静态成员函数,不能访问类中非静态成员
void* operator new(size_t size) {
cout << "调用类的new函数 申请到" << size << "字节" << endl;
void* ptr = malloc(size);
cout << "申请到的地址" << ptr << endl;
return ptr;
}
// 默认就是静态成员函数,不能访问类中非静态成员
void operator delete(void* ptr) {
cout << "调用类的delete函数" << endl;
if (ptr == nullptr) return;
free(ptr);
}
};
int main() {
int* p = new int(3);
cout << "p = " << (void*)p << ",*p = " << *p << endl;
delete p;
cout << "---------------------------------------" << endl;
People* p1 = new People(18, "李华");
cout << "p1 = " << p1 << " 姓名" << p1->name << " 年龄是" << p1->age << endl;
delete p1;
return 0;
}
实现简单内存池
预先分配一大块的内存空间,提升分配和归还的速度。减少内存的碎片。
#include <iostream>
using namespace std;
class People {
public:
int age;
int lm;
static char* m_pool; // 内存池的起始地址
// 申请的内存池中前面的9字节用作一个People对象的存放,
// 第一个字节用作标志位,1则该9个字节已经使用,否则还没有使用,后面9个字节时类似的。
static bool initPool() {
m_pool = (char*)malloc(sizeof(char)*18); // 向系统申请18的内存池
if (m_pool == nullptr) {
return false;
}
memset(m_pool, 0, 18); // 将内存池中的内容清空为0
cout << "申请的内存池首地址:" << (void*)m_pool << endl;
return true;
}
static void freePool() {
if (m_pool == nullptr) return;
free(m_pool); // 释放内存池
cout << "内存池已经释放" << endl;
}
People(int age, int lm)
{
this->age = age;
this->lm = lm;
cout << "调用有参构造函数" << endl;
}
~People() { cout << "调用析构函数" << endl; }
// 向内存池申请内存
void* operator new(size_t size) {
if (m_pool[0] == 0) {
cout << "分配了第一块内存:" << (void*)(m_pool + 1) << endl;
m_pool[0] = 1; // 标记为已经分配
return m_pool + 1; // 返回第一个对象存放地址。
}
if (m_pool[9] == 0) {
cout << "分配了第二块内存:" << (void*)(m_pool + 1) << endl;
m_pool[9] = 1; // 标记为已经分配
return m_pool + 10; // 返回第二个对象存放地址。
}
// 如果上面的都已经分配,那么就向系统申请内存
void* ptr = malloc(size);
cout << "分配系统地址内存:" << (void*)ptr << endl;
return ptr;
}
void operator delete(void* ptr) {
if (ptr == nullptr) return;
if (ptr == m_pool + 1) {
m_pool[0] = 0; // 把第一个标记为空
cout << "第一个已经释放" << endl;
return;
}
if (ptr == m_pool + 10) {
m_pool[9] = 0; // 把第一个标记为空
cout << "第二个已经释放" << endl;
return;
}
free(ptr); // 释放系统内存
}
};
char* People::m_pool = nullptr; // 静态成员初始化
int main() {
if (People::initPool() == false) return -1;
People* p1 = new People(20, 39);
cout << "p1 = " << p1 << " 姓名" << p1->lm << " 年龄是" << p1->age << endl;
People* p2 = new People(18, 31);
cout << "p2 = " << p2 << " 姓名" << p2->lm << " 年龄是" << p2->age << endl;
People* p3 = new People(34, 31);
cout << "p3 = " << p3 << " 姓名" << p3->lm << " 年龄是" << p3->age << endl;
delete p1;
delete p2;
delete p3;
People::freePool();
return 0;
}
重载括号运算符(仿函数)
- 括号运算符必须以成员函数的形式进行重载。
- 括号运算符重载函数具备普通函数全部的特征。
- 如果函数对象和全局函数同名,那么按照作用域规则选择调用函数。
函数对象的用途:
- 表面像函数,部分场景中可以代替函数,在STL中得到广泛的应用。
- 函数对象的本质是类,可以用成员变量存放更多的信息。
- 函数对象有自己的数据类型。
- 可以提供继承体系。
#include <iostream>
using namespace std;
class People {
public:
int age;
string name;
void operator()(const string& str)const {
cout << "重载函数:" << str << endl;
}
};
int main() {
People p;
p("你好");
return 0;
}
重载一元运算符
- C++规定,重载++和–的时候,如果重载函数有一个int类型的形参,编译器处理后置表达式时将调用这个重载函数。
#include <iostream>
using namespace std;
class People {
public:
int age;
string name;
People() :age(23), name("西施") {}
void show() {
cout << name << "的年龄:" << age << endl;
}
People& operator++() {
this->age++;
return *this;
}
// 不能返回局部变量的引用
// People p = p1++; // 那么是先赋值,然会在进行自加操作的。
People operator++(int) {
People temp = *this;
this->age++;
return temp;
}
};
int main() {
People p1;
p1.show();
++p1;
p1.show();
p1++;
p1.show();
return 0;
}
自动类型转换
对于内置数据类型,如果两种数据类型是兼容的,c++可以自动转换,如果从更大的精度转化为更小的数,可能会被截断或损失精度。
explicit关键字可以用于关闭类的自动转换特性
explicit Student(int age);
Student s = 8; // 错误的
Student s = Student(8); // 显示转换,可以使用
Student s = (Student)8; // 显示转换,可以使用
Student s(8); // 常规写法
- 在实际的开发中,如果强调的是构造,建议使用explicit关键字,如果强调的是类型转换,则不使用explicit。
转换函数
构造函数只用于从某种类型转换为类类型的转换,如果要进行相反的转换,可以使用特殊的运算符函数转换函数。
语法:operator 数据类型()
转换函数必须要是成员函数,不能指定返回值类型,不能有参数。
#include <iostream>
using namespace std;
class Student {
public:
int age;
string name;
float weight;
Student() {this->age = 23;this->name = "lihua";this->weight = 50.35;}
operator int() {return this->age;}
explicit operator string() {return this->name;} // 只能显示转换
operator float() {return this->weight;}
};
int main() {
Student s;
int a = s; // 隐式转换
cout << "a = " << a << endl;
string b = (string)s; // 显示转换
cout << "b = " << b << endl;
float c = float(s); // 显示转换
cout << "c = " << c << endl;
return 0;
}
要谨慎使用隐式类型转换函数。通常最好选择仅在被显式地调用才会执行的成员函数。