存在自身的属性、行为,封装在class
类是抽象的,对象是具象的
对象是由类实例化而来的
类对于某个事物的描述是抽象的,类是对一个事物的一个描述而已
操作的某类事物中一个或者多个具体的食物,就是称为对象
属性:定义
行为:函数/方法
class name{
访问控制符:
成员变量
成员函数
}
访问控制符有 public private protected
```cpp
class Animal{
public:
int age;
char name[32];
void jiao(const char *voice){
std::cout<<name<<":"<<voice<<std::endl;
}
};
实例化普通对象
Animal cat; //一个实例化的对象
使用数组实例化多个普通对象
Animal cats[10];
定义一个指针变量
Animal *cat;
//指针变量不是一个类的实例化对象,本质是一个指针。定义一个类的指针变量根本就没有实例化一个对象
类的实例化方法还有,后续继续
例子:
class Animal{
public:
int age;
char name[32];
void jiao(const char *voice){
std::cout<<name<<":"<<voice<<std::endl;
}
};
int main() {
Animal dog;
dog.age=18;
// 清零
memset(dog.name,0,sizeof(dog.name));
// 赋值
strcpy(dog.name,"xiaohu");
dog.jiao("wang wang");
return 0;
}
给成员变量和方法定义访问级别
class Teacher{
public:
char name[32];
void set_age(int age){
if(age>200 || age<0) { // 阈值判断 函数形参合法性检查
std::cout<<"age error"<<std::endl;
return;
}
_age=age; // 在类的内部访问private成员变量
}
int get_age(){
if(_age){
return _age;
}
return 0; // 0代表age不存在
}
private:
int _age;
};
Teacher t;
memset(t.name,0,sizeof(t));
strcpy(t.name,"三哥");
//t._age=18; 错误的
t.set_age(54);
std::cout<<t.get_age()<<std::endl;
面向过程程序设计:数据结构+算法
特点
问题
面向对象程序设计
特点
优势
三大特征
封装
继承
多态
// Box.h
#ifndef C___BOX_H
#define C___BOX_H
class Box {
private:
int _l,_w,_h,_s,_v;
public:
void set_l(int l);
void set_w(int w);
void set_h(int h);
int get_l();
int get_w();
int get_h();
int get_s();
int get_v();
};
#endif //C___BOX_H
// Box.cpp
#include "Box.h"
#include "iostream"
void Box::set_l(int l) {
if(l<0){
std::cout<<"l error"<<std::endl;
return;
}
_l=l;
}
void Box::set_w(int w) {
if(w<0){
std::cout<<"w error"<<std::endl;
return;
}
_w=w;
}
void Box::set_h(int h) {
if(h<0){
std::cout<<"h error"<<std::endl;
return;
}
_h=h;
}
int Box::get_l() {
return _l;
}
int Box::get_w() {
return _w;
}
int Box::get_h() {
return _h;
}
// 先判断长宽高是否存在 然后在计算 这里没有实现
int Box::get_s() {
return 2*(_l*_h+_l*_w+_h*_w);
}
int Box::get_v() {
return _l*_w*_h;
}
// main.cpp
Box b;
b.set_l(15);
b.set_h(5);
b.set_w(5);
std::cout<<"s:"<<b.get_s()<<std::endl;
std::cout<<"v:"<<b.get_v()<<std::endl;
类的属性在定义的时候不可以初始化
class Dog{
public:
int age=18; // 这是不允许的
};
构造函数(constructor)处理对象的初始化,特殊的成员函数,实例化对象的时候自动调用构造函数,作用就是在类实例化的时候初始化成员变量
与类名相同的特殊成员函数 即为构造函数
在定义的时候可以有参数,也可以没有参数
没有任何返回类型的声明
构造函数要在外部可以访问到 public
class Dog{
public:
/*
默认构造函数:如果构造函数没有被定义,编译器会自动生成一个如下的构造函数(我们没有写构造函数的时候才会自动生成)
*/
Dog(){}
};
// 实例
class Dog{
public:
//无参数构造函数
Dog(){
std::cout<<"Dog"<<std::endl;
}
Dog(int age,int id){
std::cout<<age<<"-"<<id<<std::endl;
}
private:
int _age;
char name[50];
};
Dog d1; //一次
Dog d2[4]; //四次
//不是实例化一个对象,而是声明一个函数 返回值是Dog 名字是d3 形参列表为空
Dog d3();
Dog d5(1,2); //实例化对象 只调用相应的有两个int形参的调用函数
初始化成员列表
mdata(val)
Dog(int id,int num,int age):_id(id),_num(num),_age(age){
std::cout<<"Dog:"<<std::endl;
}
作用
调用 Dog d 的时候内存已经分配空间了,此时在构造函数体内部进行的是赋值而不是初始化,利用冒号即是初始化
class C{
public:
int x;
C(int s){
x=s;
}
};
// 类中实例化一个类
class B{
public:
int a;
C n;
B():a(1),n(50){}
};
//如果下面则是错误的
class B{
public:
int a;
C n;
B():a(1){} // 报错 error: no matching function for call to 'C::C()'
};
class B{}
B b(); //不是实例化 一个返回class的b函数的声明
B (1,2);//是实例化
用于清理对象
语法:~ClassName()
class Cat{
public:
int age,id;
char name[32];
Cat(){
std::cout<<"cat"<<std::endl;
}
~Cat(){
std::cout<<"~~"<<std::endl;
}
};
// return 0;代表程序结束 也就是class要销毁了
在栈空间的变量 函数等系统会自动销毁
而在堆空间我们申请的空间系统不会自动销毁,要使用析构函数进行销毁
class Cat{
public:
int age,id;
char *name;
Cat(){
std::cout<<"Cat()"<<std::endl;
name=(char*) malloc(32); // 32B的空间 分配在堆上
}
Cat(char *name1){
std::cout<<" Cat(char *name1)"<<std::endl;
int len = strlen(name1);
//分配在堆上
name=(char *) malloc(len+1);
strcpy(name,name1);
}
~Cat(){
std::cout<<"~~"<<std::endl;
if (name!=NULL){
free(name);
}
}
};
当类中的成员变量是另外一个类的实例化对象,成员对象
成员变量所属的类中没有实现无参构造函数的时候,用初始化成员列表
class ABC{
public:
ABC(int x,int y,int z){
std::cout<<"ABC(int x,int y,int z)"<<std::endl;
}
~ABC(){
std::cout<<"~ABC()"<<std::endl;
}
};
class MyD{
public:
MyD(): abc1(1,2,3), abc2(1,1,1){
std::cout<<"MyD()"<<std::endl;
}
~MyD(){
std::cout<<"~MyD()"<<std::endl;
}
ABC abc1,abc2;
};
/*
当实例化MyD时,会先构造成员对象(按照定义顺序),然后再构造MyD自身
当销毁的时候,先销毁MyD 然后再销毁成员对象(与定义顺序相反)
*/
MyD y;// 先输出两个ABC(int x,int y,int z) 分别时abc1 abc2的 然后输出MyD()
// 销毁的时候 ~MyD() 然后两个~ABC() 分别时abc2 abc1的
也就是说
内部准备好 外部才可以准备(构造过程,内部按定义准备)
外部销毁了 内部才可以销毁(析构过程,内部反定义销毁)
在c语言中使用malloc和free
在c++中一般使用 new delete
基本语法使用
new
//new动态分配堆内存
使用形式:指针常量 = new 类型(常量);
指针常量 = new 类型[表达式];
作用:从堆分配一块类型大小的存储空间,返回首地址
其中:常量是初始化的值可省略
创建数组对象时,不能为对象指定初始值
// 例子
//在堆内存申请一个int(4b)大小的空间,并初始化为10
int *p = new int(10);
//在堆内存申请四个int(4*4b)大小的空间
int *q = new int[4];
// 在堆上申请一个Box类型大小的空间,会进行实例化对象
Box *x = new Box;
Box *y = new Box(10,10,10);
delete
防止内存泄漏
delete 释放已分配的堆内存空间
使用形式:delete 指针变量;
delete[] 指针变量;
其中:指针变量 必须是new返回的指针
delete x;
delete[] xs;
c++为了兼容c保留了malloc和free,但是不建议使用。
注意:new和delete是运算符,不是函数,因此执行效率更高
malloc/free是c的标准库的函数,new/delete是c++的运算符。调用函数要有函数调用栈 有消耗比运算符效率低
new能自动计算需要分配的内存空间,而malloc需要手动计算字节数
int *p = new int[4]; int *q = (int *)malloc(4*sizeof(int));
new和delete直接带具体类型的指针,而malloc和free返回void类型的指针(强制类型转化)
int *p = new int[4]; int *q = (int *)malloc(4*4);
malloc 要强制类型转化new类型是安全的,malloc不是。
new 调用构造函数 而malloc不能 malloc可能给一些无法初始化
delete 调用析构函数 而free不能 free可能导致内存泄露
区别在以下几个方面
会存在一些问题!! 如果类里面成员变量有指针会出现问题,同一指针指向同一空间
如下代码和图片
class Test{
public:
int x,y;
int *sum;
Test(){
std::cout<<"Test()"<<std::endl;
x=0;
y=0;
sum = new int[4];
}
Test(int a,int b):x(a),y(b){
std::cout<<"Test(int a,int b)"<<std::endl;
sum = new int[4];
}
};
Test *t1 = new Test(10,10);
Test t2 = *t1;
std::cout<<t1->x<<std::endl;
std::cout<<t2.x<<std::endl;
for (int i =0;i<4;++i){
t1->sum[i]=i;
}
std::cout<<t2.sum[0]<<std::endl;
// 手动销毁t1开辟的堆空间-- 意味着销毁*t1这个对象 自动调用析构函数 顺带手动释放成员变量申请的堆地址
delete t1;
//此时再访问t2的sum,数据会乱 因为指向了被回收的空间
// 除此t1销毁的时候释放sum空间 但在t2销毁的时候又释放一次sum,造成了同一块堆空间的二次释放 第二次释放的时候会出现异常 后续的代码就不会正常执行
std::cout<<t2.sum[0]<<std::endl;
那么怎么解决上述问题?
一种方法:手动
Test t2;
t2.x=t1.x;
t2.y=t1.y;
for (int i=0;i<4;++i)
t2.sum[i]=t1.sum[i];
另外方法:拷贝构造函数
当使用Test t2=t1
的时候,不会调用构造函数,而是调用拷贝构造函数(如果我们没有写,编译器自动生成一个拷贝构造函数)
class Test{
public:
int x,y;
int *sum;
...
// 拷贝构造函数
Test(const Test &t){ // 参数是引用的右值 此例子中也就是t1的引用
std::cout<<"Test(const Test &t)"<< std::endl;
/*
* 编译器自动帮我们实现的是:
* x=t.x;
* y=t.y;
* sum=t.sum;
* */
x = t.x;
y = t.y;
sum=new int[4]; // 新开辟一段空间
// 将t1.sum空间的内容拷贝到sum所指向的空间
for(auto i=0;i<4;++i)
sum[i]=t.sum[i];
}
};
Test t1(10,10);
t1.sum[0]=100;
t1.sum[1]=101;
t1.sum[2]=102;
t1.sum[3]=103;
Test t2=t1;
//地址不同
std::cout<<t1.sum<<std::endl;
std::cout<<t2.sum<<std::endl;
1、为什么拷贝构造函数的形参是引用?Test(const Test &t)
因为如果是这样的话Test(const Test t)
,也就是会调用一下Test t = t1
这样会触发拷贝构造函数,会造成无限循环调用拷贝构造函数,所以必须是引用。
除此 还会新开辟空间生成t1 造成空间浪费
2、为什么拷贝构造函数的形参是必须用const修饰?Test(const Test &t)
保护右值,即防止t1对象被修改
同一类型的对象之间可以赋值,使得两个对象的成员变量的值相同,两个对象依然是独立的两个对象,这种情况叫做浅拷贝
一般情况,浅拷贝没有任何副作用,但是当成员变量有指针,并且指针指向动态分配的内存空间,这样的浅拷贝会导致两个对象的指针指向同一空间,当两个对象销毁的时候会调用析构函数,造成同一空间被释放两次从而导致程序运行出错。
如果我们没有实现拷贝构造函数,c++编译器会自动实现一个拷贝构造函数,是默认拷贝构造函数 是浅拷贝。
自己手动实现拷贝构造函数,在拷贝构造函数中需要对对象中的指针变量进行单独的内存申请。
两个对象中的指针变量不会指向同一块内存空间,然后再将右值对象指针所指向的空间中的内容拷贝到新的对象指针所指向的堆空间中。
好处:
中间没有临时对象的产生,提高了程序执行的效率。
注意事项:
防止值被修改,也就是为了保护传入的值,应该const修饰 使其为只读
在c语言中,数据和处理数据的操作(函数)是分开声明的,也就是说,语言本身并没有数据和函数的关联性。
在c++中,通过抽象数据类型,在类中定义数据和函数,实现数据和函数直接的绑定
class C1{
public:
int x;
int y;
int k;
protected:
private:
};
class C2{
public:
int x,y,k;
int getK(){return k;}
void setL(int mK){k=mK;}
};
C1 c1;
C2 c2;
std::cout<<"c1:"<<sizeof(c1)<< std::endl; //12
std::cout<<"c2:"<<sizeof(c2)<< std::endl; //12
上面的代码 输出的c1和c2的占用空间大小竟然是相同的!!
也就是说,C++类对象中的成员变量和成员函数是分开存储的
成员变量
成员函数
内存从上到下可以大致分为:栈区 堆区 数据段 代码段
所有类实例化的对象共享处于代码段中的成员函数,但是成员函数怎么直到是谁调用的它呢?这就引出了this指针
this指针就是指向调用该成员函数的对象
gdb调试
C1 c1;
c1.k=10;
C2 c2;
c2.k=100;
c2.getK();
1、g++ -g main.cpp -o main
2、gbd main
3、start n(next)print c2.getK quit(退出)
这里的参数就是this指针 编译器自动生成的,this指针指向调用方法的对象
c.getK(); ? getK(&c); 传入对象c的地址,传给了C *const this(成员函数的第一个形参) this指向c的地址
关键字 static 声明一个类的成员
静态成员提供一个同类对象的共享机制
这个类无论有多少个对象被创建,这些对象共享这个static成员
static的作用域是类和对象之间,但不是对象的成员
调用
class Sheep{
public:
char name[55];
int age;
static int cnt; //这里只是声明 定义和初始化要放到全局
Sheep(){
cnt++;
}
~Sheep(){
cnt--;
}
};
int Sheep::cnt;//定义sheep类的静态成员变量cnt 并且初始化(如果不初始化默认为0)
// main函数
Sheep *p = new Sheep[10];
//访问静态成员变量
std::cout<<Sheep::cnt<<std::endl;
std::cout<<p->cnt<<std::endl;
使用static修饰的成员函数
在静态成员函数内不能访问除静态成员变量以外的其他成员变量(只能访问静态成员变量)
调用方法
class Sheep{
public:
char name[55];
int age;
static int cnt; //这里只是声明 定义和初始化要放到全局
//静态成员函数
static int sheepNum(){
//std::cout<<age;//报错,静态成员函数不能访问非静态的成员变量
return cnt; //静态成员属性
}
Sheep(){
cnt++;
}
~Sheep(){
cnt--;
}
};
int Sheep::cnt=0;//定义sheep类的静态成员变量cnt 并且初始化(如果不初始化默认为0)
// main函数中
Sheep s1;
std::cout<<Sheep::sheepNum();
存在的意义?
c语言中使用字符数组表示字符串的,这样效率比较低
C++标准库力通过 类string 重新自定义了字符串
#include <string>
std::string s;//空字符串
std::string s1("hello"); //用字符串常量构造string对象 调用的构造函数 string(const char*)
std::string s2(4,'k');//kkkk
std::string s3("123456",1,3);//从str下标为pos的开始去n个字符 从"123456" 下标为1的2数3个字符
std::cout<<s.size()<<std::endl; //字符串长度
std::cout<<s.length()<<std::endl; //字符串长度
//下面是错误的
std::string s4('A'); //传入一个char类型
std::string s5(123); //传入一个int类型
std::string s6(123.2365); //传入一个double类型
1、可以使用char*
类型的变量、常量,char
类型的常量对string
进行赋值
std::string s;
s = "hello";
s='A'; //赋值的是字符A 但是s是字符串
char name[32];
strcpy(name,"du");
s=name;
2、assign
成员函数
std::string s1("string"),s2;
s2.assign(s1);//s2=s1
s2.assign("ddddd");
s2.assign(s1,1,3);//s2=s1下标从1开始 取3个
s2.assign(3,'a');//aaa
// 直接使用 +
std::string s1("+"),s2;
s2 = '1'+s1+'2'; //1+2
to_string(val); //将int double bool转化为string
// append成员函数,返回对象自身的引用
s2.append(s1); // 将s1追加到s2后面 s2=s2+s1
s2.append(s1,1,3); //下标 1 数量 3(追加字串一般用append)
s2.append(3,'k'); // 追加3个k
1、直接使用比较的运算符比较大小(基于ascii比较)
string s1("hello"),s2("world");
bool ret = s1>s2;
2、compare成员函数
主要是用于指定字符串的子串进行比较大小
s1.compare(1,3,s2,1,3); //s1从下标1开始数3个字符的字串与s2从下标1开始数3个字符的字串比较
substr成员函数可以用于求子串,函数原型:
string substr(int n=0,int m=string::npos) const;
string s1 = s.substr(0, 2);//从s的索引为0的位置截取2个长度的字符串
string s2 = s.substr(2, 3);//从s的索引为2的位置截取3个长度的字符串
string s3 = s.substr(2);//如果只提供一个索引位置信息,那么截取s索引为2到字符串尾部
string s4 = s.substr();//如果不提供任何信息,那么等价于s3=s
string s5 = s.substr(7);//按理s的索引最多到6,但是组成字符串默认最后一位为空字符,所以索引可以到7
查不到返回string::npos
,是string定义的一个静态常量
find
:从前往后查找字串或字符出现的位置 *(比较重要)rfind
:从后往前查找字串或字符出现的位置find_first_of
:从前往后查找何处第一次出现另一个字符串中包含的字符find_last_of
:从后往前查找何处第一次出现另一个字符串中包含的字符find_first_not_of
:从前往后查找何处第一次出现另一个字符串中没有包含的字符find_last_not_of
:从后往前查找何处第一次出现另一个字符串中没有包含的字符查不到返回string::npos
,是string定义的一个静态常量
replace
成员函数可以对string对象中的字串进行替换,返回对象自身的引用
replace(int pos,int n,string s,[int pos],[int n]);
//c++ 对单引号和双引号区分特别严重
string s("hello world");
s.replace(1,4,"i"); //hi world
s.replace(1,1,2,'i'); //hii world
s.replace(1,2,"abcdefghijk",8,1);//hi world
erase
删除string对象的子串,返回对象自身的引用
string s("real dog");
s.erase(0,4);// dog 删除从下标0开始 4个字符
s.erase(2);// d 删除从下标2开始到结尾的全部字符
// 删除字符串中的steel
// 删除字符串中的steel
std::string s("sdjaldsteeldsakdssteel"),moom("steel");
while (s.find(moom)!=std::string::npos){
s.erase(s.find(moom),moom.length());
}
std::cout<<s<<std::endl;
insert
成员函数可以在 string 对象中插入另一个字符串,返回值为对象自身的引用
string s("limit");
s.insert(2,"123");//li123mit 在1-2之间插入
s.insert(1,5,'x');//lxxxxxi123mit 在0-1之间插入5个x
s.insert(3,"hello",1,3);// 在2-3之间插入hello 1到1之后的3个 子串
Array.h
//
// Created by DELL on 2024-01-04.
//
#ifndef ARRAY_ARRAY_H
#define ARRAY_ARRAY_H
#define DefaultLen 4
using u32_t = unsigned int;
class Array {
private:
int *data;
u32_t length; //数组当前长度
u32_t capacity;//数组容量
static int indexMax;
//自动扩容函数
void extend();
public:
//默认构造函数
Array();
//带参数的构造函数
Array(u32_t len);
//拷贝构造函数
Array(const Array &arr);
//析构函数
~Array();
//获得数组当前长度
u32_t getLength();
//获取数组容量
u32_t getCapacity();
//返回指定位置的值
int getValue(u32_t index);
//返回指定值的下标
int getIndex(int value);
//销毁动态数组
void destroy();
//在指定位置插入元素
bool insert(u32_t i,int value);
//删除指定位置元素
void remove_index(u32_t i);
//删除指定值的元素
void remove_value(int value);
};
#endif //ARRAY_ARRAY_H
Array.cpp
//
// Created by DELL on 2024-01-04.
//
#include <iostream>
#include "Array.h"
#include "string"
int Array::indexMax = 999999;
using namespace std;
Array::Array() {
this->data=new int[DefaultLen];//默认分配4个int类型的空间
this->length=0; //当前长度为0
this->capacity=DefaultLen;//容量为DefaultLen
}
Array::Array(u32_t len){
this->data=new int[len];
this->length=0;
this->capacity=len;
}
//拷贝构造函数 实现深拷贝
Array::Array(const Array &arr) {
this->data=new int [arr.capacity];
this->length = arr.length;
this->capacity = arr.capacity;
for (int i = 0; i < length; ++i) {
this->data[i]=arr.data[i];
}
}
Array::~Array() {
if(this->data!= nullptr){
delete[] this->data;
}
}
void Array::extend() {
//临时指针保存堆空间的首地址
int * tmp = data;
data = new int [2*capacity];
for (int i = 0; i < length; ++i) {
data[i] = tmp[i];
}
delete[] tmp;
capacity *=2;
}
void Array::destroy() {
if (data){
delete[] data;
data= nullptr; // 这里将data指针置空,防止析构函数二次销毁
length=0;
capacity=0;
}
}
u32_t Array::getLength() {
return length;
}
u32_t Array::getCapacity() {
return capacity;
}
int Array::getValue(u32_t index) {
if(index<length) return data[index];
cout<<"index out of range"<<endl;
return indexMax;
}
bool Array::insert(u32_t i, int value) {
//i 和 capacity 的大小关系
if(length==capacity)extend();
if(i>=capacity){
data[length] = value;
}
else{
int j=length;
for (; j > i; --j) {
data[j] = data[j-1];
}
data[j]=value;
}
length++;
return true;
}
int Array::getIndex(int value) {
for (int i = 0; i < length; ++i) {
if (data[i]==value)return i;
}
cout<<"not find value:"<<value<<endl;
return -1;
}
void Array::remove_index(u32_t i) {
if(i>=length){
cout<<"index out of range"<<endl;
return;
}
for (int j = i; j < length; ++j) {
data[j] = data[j+1];
}
length--;
}
void Array::remove_value(int value) {
int pos = getIndex(value);
while (pos>=0){
remove_index(pos);
pos= getIndex(value);
}
}
main.cpp
#include <iostream>
#include "Array.h"
using namespace std;
/*
* Array动态数组设计,功能(存储整型数据)
* 1.自动扩容
* 2.销毁数组
* 3.在指定位置插入元素
* 4.删除指定位置的元素
* 5.删除指定值的元素
* */
int main() {
Array t(5);
t.insert(0,0);
t.insert(1,1);
t.insert(2,2);
t.insert(4,3);
t.insert(4,4);
t.insert(500,5);
t.insert(8,0);
// t.remove_index(20);
// t.remove_index(2);
t.remove_value(0);
// t.getValue(100);
// t.destroy();
for (int i = 0; i < t.getLength(); ++i) {
cout<<t.getValue(i)<<ends;
}
cout<<endl;
cout<<t.getLength()<<endl;
cout<<t.getCapacity()<<endl;
return 0;
}