【C++】认识类和对象

news2025/2/24 2:35:23

在这里插入图片描述
🔥博客主页 小羊失眠啦.
🎥系列专栏《C语言》 《数据结构》 《C++》 《Linux》 《Cpolar》
❤️感谢大家点赞👍收藏⭐评论✍️


在这里插入图片描述

文章目录

  • 一、什么是面向对象?
  • 二、类的引入
  • 三、类的定义
  • 四、类的访问限定符与封装
    • 4.1 访问限定符
    • 4.2 封装
  • 五、类的作用域
  • 六、类的实例化
  • 七、类对象模型
  • 八、this指针
    • 8.1 this指针的特性
  • 九、C语言与C++实现栈的对比
    • 9.1 C语言实现
    • 9.2 C++实现

一、什么是面向对象?

学习C语言时,我们就经常听说C语言是面向过程的,那么什么是面向过程呢?举个例子,我们现在要完成洗衣服的工作,此刻我们关注的是洗衣服的过程:

  1. 拿个盆子;

  2. 接水;

  3. 放衣服;

  4. 到洗衣粉

  5. 在这里插入图片描述

    C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题

    C++是面向对象的,关注的是对象,在解决问题时,通过对象间的交互来完成,每个对象负责不同的工作。


二、类的引入

C语言中,结构体内只能定义变量,在C++中,结构体内不仅可以定义变量,还可以定义函数

以前用C语言实现数据结构——栈时,我们这样定义:

//C语言
typedef int DataOfStackType;

typedef struct stack
{
	DataOfStackType* a;
	int top;
	int capacity;
}stack;

void StackInit(stack* ps);

void StackPush(stack* ps, DataOfStackType data);

void StackPop(stack* ps);

//...

而在C++中,我们可以这样定义:

//C++
typedef int DataOfStackType;

typedef struct stack
{
	void StackInit(stack* ps);

	void StackPush(stack* ps, DataOfStackType data);

	void StackPop(stack* ps);
//...

	DataOfStackType* a;
	int top;
	int capacity;
}stack;

像上面的定义方式,C++中更喜欢用一个新的名字——class来代替struct

class stack
{
	void StackInit(stack* ps);

	void StackPush(stack* ps, DataOfStackType data);

	void StackPop(stack* ps);
//...

	DataOfStackType* a;
	int top;
	int capacity;
};

命名规则建议:C++类的成员我们习惯在前面加一个_(来区分类的对象和传入的参数)


三、类的定义

class className
{
	//...类的主体:由成员函数和成员变量组成
};//一定要注意后面的分号

如上所示,类的定义与结构体的定义非常相似,但我们还是得认识几个专有名词。

  • class :定义类的关键字;
  • class Name:类的名字;
  • 类的主体:{}中为类的主体,由成员变量成员方法组成;
  • 成员变量:又称类的属性,指在类中定义的变量;
  • 成员函数:又称类的方法,指在类中定义的函数;

类的定义方式一般有两种:

  1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。

    //日期类
    class Date
    {
    	void Init(int year, int month, int day)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	void Print()
    	{
    		cout << _year << "-" << _month << "-" << _day << endl;
    	}
    
    	int _year;
    	int _month;
    	int _day;
    };
    
  2. 类的声明放在.h文件中成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::

    //Date.h文件中声明类
    //日期类
    class Date
    {
    public:
    
    	void Init(int year, int month, int day);
    	void Print();
    	
    	int _year;
    	int _month;
    	int _day;
    };
    
    //Date.c文件中定义成员函数
    #include"Date.h"
    
    void Date::Init(int year, int month, int day)
    {
    	_year = year;
    	_month = month;
    	_day = day;
    }
    void Date::Print()
    {
    	cout << _year << "-" << _month << "-" << _day << endl;
    }
    

一般情况下,更期望采用第二种方式。至于其中的public是什么意思,这个等下再说。


四、类的访问限定符与封装

4.1 访问限定符

我们在类中定义了各种成员函数与成员变量,有时候,我们不想让别人随便访问类中的某些成员,比如成员变量,但其它的成员对外开放,比如成员函数,那么我们就需要用到访问限定符来修饰这些成员。

访问限定符有三个:publicprotectedprivate

在这里插入图片描述

访问限定符说明

  • class的默认访问权限为privatestructpublic(因为struct要兼容C);
  • public修饰的成员在类外可以直接被访问
  • protectedprivate修饰的成员在类外不能直接被访问(此处 protected 和 private 是类似的);
  • 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止;
  • 如果后面没有访问限定符,作用域就到 } 即类结束。

注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。

一道面试题

问题:C++中 struct 和 class 的区别是什么?

解答: C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private

4.2 封装

面向对象有三大特性:封装、继承、多态。在当前阶段,我们只学习封装的特性。

  • 什么是封装呢?

用专业一点的话来回答就是:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互

封装本质上是一种管理,让用户更方便使用类。比如:对于电脑这样一个复杂的设备,提供给用户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。

对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等让用户可以与计算机进行交互即可

在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用


五、类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 ::作用域操作符指明成员属于哪个类域。

class Person
{
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender[3];
	int  _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
	cout << _name << " "<< _gender << " " << _age << endl;
}

六、类的实例化

用类创建对象的过程,称为类的实例化。

//类的声明
class Date
{
public:

	void Init(int year, int month, int day);
	void Print();

	int _year;
	int _month;
	int _day;
};

有这样一个问题

  • 上面实现的Date类占用多大的内存空间?

答案是,不占用空间。因为定义类只是一个描述对象的过程

类实例化出对象就像现实中使用设计图建造出房子,就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,而实例化出的对象才是真正盖好的房子,能实际存储数据,占用物理空间。

像设计图一样,并不是一幅设计图只能盖一个房子,一个类也可以实例化出多个对象

class Date
{
public:
	//...
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2;
	Date d3;
	cout << sizeof(d1) << endl;
	cout << sizeof(d2) << endl;
	cout << sizeof(d3) << endl;
	return 0;
}

在这里插入图片描述

  • 类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它
  • 一个类可以实例化出多个对象,实例化出的对象占用 实际的物理空间,存储类成员变量

七、类对象模型

我们刚刚用sizeof观察了实例化出来的对象确实占用了内存空间,但是我们并不知道结果为什么是 12 。

上文已经提到,C++中,类与结构体本质是相同的。为了兼容C语言,C++肯定不能改变结构体大小的计算规则,而类与结构体相同(除默认访问限定符不同),那么类当然也是运用与结构体相同的计算规则

  • 类中定义的成员函数不会占用空间吗?

答案是并不占用,因为成员函数公共代码段

简单写个程序验证一下:

// 类中既有成员变量,又有成员函数
class A1 {
public:
	void f1() {}
private:
	int _a;
};

// 类中仅有成员函数
class A2 {
public:
	void f2() {}
};

// 类中什么都没有---空类
class A3
{};

int main()
{
	cout << sizeof(A1) << endl;
	cout << sizeof(A2) << endl;
	cout << sizeof(A3) << endl;

	return 0;
}

在这里插入图片描述

结果显而易见,仅有成员函数的类与空的类大小是相同的,那么就验证了上面的说法。但是又出来一个新的问题:

  • 为什么空类的大小为 1 呢?

答案是,空类比较特殊,编译器会给空类一个字节来唯一标识这个类的对象

总结

一个类的大小,实际上是该类中成员变量的大小之和,当然与结构体相同,需注意内存对齐空类须特殊处理,大小为1字节。

在C语言阶段,博主已经对结构体的内存对齐做了超级详细的讲解,若有遗忘的小伙伴点击此处跳转。

以下是对内存对齐的简单回忆。

结构体内存对齐规则

  1. 第一个成员在与结构体偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的对齐数8
  3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

八、this指针

在类的成员函数中,有这样一个存在,我们无时无刻不在用它,但是我们却看不见它,这个神奇的存在就是this指针

我们如何证明它存在呢?首先定义一个日期类。

//日期类
class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1, d2;
	d1.Init(2022, 1, 11);
	d2.Init(2022, 1, 12);
	d1.Print();
	d2.Print();
	return 0;
}

对于上述操作,有这样一个疑问:

  • Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?

这就不得不提我们刚才谈到的this指针了。C++编译器给每个非静态的成员函数增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成

8.1 this指针的特性

在这里插入图片描述

this指针有以下特性:

  • this指针的类型:类的类型* const,即成员函数中,不能给this指针赋值;
void Init(int year, int month, int day)
{
	//错误示例
	this=nullptr;
}
  • 只能在成员函数的内部使用;
  • this指针本质上是成员函数的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针
  • this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递;

有了this指针,我们上面实现的日期类还可以这样实现。

//日期类
class Date
{
public:
	void Init(int year, int month, int day)
	{
			this->_year = year;
			this->_month = month;
			this->_day = day;
	}
	void Print()
	{
		cout <<this-> _year << "-" <<this-> _month << "-" <<this-> _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

但是在实际写代码的过程中没有必要这样写,既然编译器已经帮我们做了,我们就不要干吃力不讨好的事情。

到目前为止,我们已经简单的认识了类和对象,更多关于类和对象的内容还没有讲到。认识了类与对象,我们就简单对比一下C语言实现栈和C++中运用类实现的过程。


九、C语言与C++实现栈的对比

9.1 C语言实现

typedef int DataType;

typedef struct Stack
{
	DataType* array;
	int capacity;
	int size;
}Stack;

void StackInit(Stack* ps)
{
	assert(ps);
	ps->array = (DataType*)malloc(sizeof(DataType) * 3);
	if (NULL == ps->array)
	{
		assert(0);
		return;
	}
	ps->capacity = 3;
	ps->size = 0;
}

void StackDestroy(Stack* ps)
{
	assert(ps);
	if (ps->array)
	{
		free(ps->array);
		ps->array = NULL;
		ps->capacity = 0;
		ps->size = 0;
	}
}

void CheckCapacity(Stack* ps)
{
	if (ps->size == ps->capacity)
	{
		int newcapacity = ps->capacity * 2;
		DataType* temp = (DataType*)realloc(ps->array,
			newcapacity * sizeof(DataType));
		if (temp == NULL)
		{
			perror("realloc申请空间失败!!!");
			return;
		}
		ps->array = temp;
		ps->capacity = newcapacity;
	}
}

void StackPush(Stack* ps, DataType data)
{
	assert(ps);
	CheckCapacity(ps);
	ps->array[ps->size] = data;
	ps->size++;
}

int StackEmpty(Stack* ps)
{
	assert(ps);
	return 0 == ps->size;
}

void StackPop(Stack* ps)
{
	if (StackEmpty(ps))
		return;
	ps->size--;
}

DataType StackTop(Stack* ps)
{
	assert(!StackEmpty(ps));
	return ps->array[ps->size - 1];
}

int StackSize(Stack* ps)
{
	assert(ps);
	return ps->size;
}

int main()
{
	Stack s;
	StackInit(&s);
	StackPush(&s, 1);
	StackPush(&s, 2);
	StackPush(&s, 3);
	StackPush(&s, 4);
	printf("%d\n", StackTop(&s));
	printf("%d\n", StackSize(&s));
	StackPop(&s);
	StackPop(&s);
	printf("%d\n", StackTop(&s));
	printf("%d\n", StackSize(&s));
	StackDestroy(&s);
	return 0;
}

可以看到,在用C语言实现时,Stack相关操作函数有以下共性:

  • 每个函数的第一个参数都是Stack*
  • 函数中必须要对第一个参数检测,因为该参数可能会为NULL
  • 函数中都是通过Stack*参数操作栈的
  • 调用时必须传递Stack结构体变量的地址

结构体中只能定义存放数据的结构,操作数据的方法不能放在结构体中,即数据和操作数据的方式是分离开的,而且实现上相当复杂一点,涉及到大量指针操作,稍不注意可能就会出错。

9.2 C++实现

typedef int DataType;
class Stack
{
public:
	void Init()
	{
		_array = (DataType*)malloc(sizeof(DataType) * 3);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = 3;
		_size = 0;
	}
	void Push(DataType data)
	{
		CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	void Pop()
	{
		if (Empty())
			return;
		_size--;
	}
	DataType Top() { return _array[_size - 1]; }
	int Empty() { return 0 == _size; }
	int Size() { return _size; }
	void Destroy()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	void CheckCapacity()
	{
		if (_size == _capacity)
		{
			int newcapacity = _capacity * 2;
			DataType* temp = (DataType*)realloc(_array, newcapacity *
				sizeof(DataType));
			if (temp == NULL)
			{
				perror("realloc申请空间失败!!!");
				return;
			}
			_array = temp;
			_capacity = newcapacity;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};

int main()
{
	Stack s;
	s.Init();
	s.Push(1);
	s.Push(2);
	s.Push(3);
	s.Push(4);
	printf("%d\n", s.Top());
	printf("%d\n", s.Size());
	s.Pop();
	s.Pop();
	printf("%d\n", s.Top());
	printf("%d\n", s.Size());
	s.Destroy();
	return 0;
}

C++中通过类可以将数据 以及 操作数据的方法进行完美结合,通过访问权限可以控制那些方法在类外可以被调用,即封装,在使用时就像使用自己的成员一样,更符合人类对一件事物的认知。而且每个方法不需要传递Stack*的参数了,编译器编译之后该参数会自动还原,即C++中 Stack *参数是编译器维护的,C语言中需用用户自己维护。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1477928.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一文速览深度伪造检测(Detection of Deepfakes):未来技术的守门人

一文速览深度伪造检测&#xff08;Detection of Deepfakes&#xff09;&#xff1a;未来技术的守门人 前言一、Deepfakes技术原理卷积神经网络&#xff08;CNN&#xff09;&#xff1a;细致的艺术学徒生成对抗网络&#xff08;GAN&#xff09;&#xff1a;画家与评审的双重角色…

异常网络下TCP的可靠服务机制(慢启动、拥塞避免、快重传、快恢复)

目录 TCP超时重传拥塞控制概述慢启动和拥塞避免下面讲解发送端如何判断拥塞发生。 快速重传和快速恢复 本文描述TCP在异常网络下的处理方式 以保证其可靠的数据传输的服务 TCP超时重传 tcp服务能够重传其超时时间内没有收到确认的TCP报文段&#xff0c;tcp模块为每一个报文段都…

【机器学习】特征选择之包裹式特征选择法

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;机器学习 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进…

matlab实现不同窗滤波器示例

1 汉明窗低通滤波器 &#xff1a; 在Matlab中使用汉明窗设计低通滤波器可以通过fir1函数实现。汉明窗通常用于设计滤波器&#xff0c;可以提供更突出的频率特性。 下面是一个示例代码&#xff0c;演示如何在Matlab中使用汉明窗设计低通滤波器&#xff1a; % 定义滤波器参数 fs …

数据结构——lesson4带头双向循环链表实现

前言✨✨ &#x1f4a5;个人主页&#xff1a;大耳朵土土垚-CSDN博客 &#x1f4a5; 所属专栏&#xff1a;数据结构学习笔记​​​​​​ &#x1f4a5;双链表与单链表的区分&#xff1a;单链表介绍与实现 &#x1f4a5;对于malloc函数有疑问的:动态内存函数介绍 感谢大家的观看…

J1—Vivado调试技巧VIO IP

1.简介 VIO&#xff08;Virtual Input/Output&#xff09;IP核是一种用于FPGA设计的IP核&#xff0c;它可以模拟输入/输出设备的功能&#xff0c;如键盘、鼠标、显示器等。VIO IP核可以在FPGA设计中用于调试和验证&#xff0c;帮助工程师快速定位问题并进行调试。如图所示&…

vue 解决:点击左侧相同菜单,右侧页面不重新加载的问题

1、问题描述&#xff1a; 其一、需求为&#xff1a; 无论是通过路由组件形成的平台管理系统&#xff0c;还是通过文件配置形成的平台管理系统&#xff0c;都存在通过切换左侧的导航栏而使右侧的页面切换的业务需求&#xff1b; 其二、问题描述为&#xff1a; A、步骤一&#…

全国产飞腾E2000Q +复旦微FPGA的轨道交通、电力解决方案

产品概述 ITX-XMF201是一款高性能边缘计算网关主板&#xff0c;采用飞腾E2000Q 4核处理器&#xff0c;国产化率达到95%国产化。 板载2电口&#xff0c;2路CAN&#xff0c;6路RS232接口&#xff0c;1路RS485接口&#xff0c;16路GPIO&#xff0c;可以满足银行、轨道交通、电力等…

springboot2入门到实战 - JWT

JWT是什么&#xff1f; JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object。 This information can be verified and trusted because it is digi…

便签软件哪个好用?好用便签怎么设置提醒?

在当今信息爆炸的时代&#xff0c;便签软件成为了人们生活中不可或缺的工具之一。那么&#xff0c;便签软件哪个好用呢&#xff1f;下面为您推荐几款备受好评的便签软件。首先是知名度极高的好用便签&#xff0c;它拥有强大的笔记功能、提醒功能和多端同步&#xff0c;让您随时…

COMPOSER安装使用WIN下升级PHP-V

想用TP6使用phpspreadsheet但是说我PHP版本低&#xff0c;原来是PHP7.0 composer要求至少7.4 直接修改环境变量&#xff0c;把PHP目录切换到7.4 composer升级比较简单&#xff0c;在PHP目录下CMD然后官网的命令执行下即可 下面就可以在TP根目录下执行命令安装PHPSPREADSHEET…

Java进阶-集合(3)与泛型

这次介绍集合中的Iterator迭代器&#xff0c;以及泛型。简单来说&#xff0c;泛型对集合的元素类型进行了限制&#xff0c;使用泛型可以在编译时检查类型安全&#xff0c;提高代码的重用率。内容如下 一、Iterator迭代器 1、概念 Iterator迭代器是一个接口&#xff0c;作用…

MATLAB环境下脑电信号EEG的谱分析

脑电信号一直伴随着人类的生命&#xff0c;脑电波是脑神经细胞发生新陈代谢、离子交换时细胞群兴奋突触电位总和&#xff0c;脑电信号的节律性则和丘脑相关&#xff0c;含有丰富的大脑活动信息。通常我们所接触的脑电图都是头皮脑电图&#xff0c;在有些特殊场合还需要皮下部位…

TikTok网络相关问题详解来了,附原生住宅代理IP供应商推荐,

想要迈过TikTok新手门槛&#xff0c;首先必须要学习的就是网络问题。很多人开始做TikTok账号或者TikTok小店时&#xff0c;都会遇到一些先前没有遇到的词汇和概念&#xff0c;比如原生IP&#xff0c;独享IP&#xff0c;甚至专线&#xff0c;那么一个IP可以做几个账号呢&#xf…

0粉低成本带货!职人号矩阵正成为商家的香饽饽

近年来&#xff0c;中国消费市场变化不断&#xff0c;线上消费持续上涨&#xff0c;线上线下一体化成为零售行业的发展新趋势。 加上抖音等平台都在大力发展本地生活&#xff0c;众多连锁商家、本地商家、百货商场纷纷加快数字化转型步伐&#xff0c;掘金线上海量流量&#xff…

机器学习:原理、应用与未来展望

第一章 是什么 机器学习&#xff08;Machine Learning&#xff09;是一门跨学科的学科&#xff0c;它使用计算机模拟或实现人类学习行为&#xff0c;通过不断地获取新的知识和技能&#xff0c;重新组织已有的知识结构&#xff0c;从而提高自身的性能。机器学习涉及多个学科&am…

HGAME 2024 WEEK4 WP

文章目录 IOTez7621 MISCezKeyboardMaybezip**Mondrians &#x1f511; REchange webReverse and Escalation. 想念21和22年的平台和week4的 6557225了 IOT ez7621 拿到固件直接binwalk解&#xff0c;之后grep出hgame 在usr/lib/opkg/info/kmod-flag.control找到这个&#x…

Quartz 任务调度框架源码阅读解析

概念: quartz 是一个基于JAVA的定时任务调度框架 案例: <dependency><groupId>org.quartz-scheduler</groupId><artifactId>quartz</artifactId><version>2.3.0</version></dependency>JobDetail job JobBuilder.newJob(Sc…

TP6上传图片到OSS(记录贴)

1&#xff0c;先安装&#xff0c;我使用composer安装 在项目的根目录运行composer require aliyuncs/oss-sdk-php 2,安装成功以后vendor目录下可以看到如图&#xff1a; 3&#xff0c;上传图片代码如下&#xff1a; <?php namespace app\controller;use app\BaseControll…

宝塔FTP服务设置并结合cpolar内网穿透实现远程传输文件

文章目录 1. Linux安装Cpolar2. 创建FTP公网地址3. 宝塔FTP服务设置4. FTP服务远程连接小结 5. 固定FTP公网地址6. 固定FTP地址连接 宝塔FTP是宝塔面板中的一项功能&#xff0c;用于设置和管理FTP服务。通过宝塔FTP&#xff0c;用户可以创建FTP账号&#xff0c;配置FTP用户权限…