2.类与对象(上篇)

1.面向过程和面向对象初步认识

C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。

image-20221209130612175

2.类的引入

C**语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。**比如:之前在数据结初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现,会发现struct中也可以定义函数

// 属性 + 方法     属性指的就是数据(成员变量),方法指的就是函数
// struct 升级
// 1、兼容c中struct 的所有用法
// 2、升级成了类

// c++中struct的用法
typedef int DataType;
struct Stack              // stack是类名
{
	// 成员函数/成员方法(方法)
	void Init(size_t capacity)
	{
		_array = (DataType*)malloc(sizeof(DataType)* capacity);
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}

		_capacity = capacity;
		_size = 0;
	}

	void Push(const DataType& data)
	{
		// 扩容
		_array[_size] = data;
		++_size;
	}

	DataType Top()
	{
		return _array[_size - 1];
	}

	void Destroy()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}

	// 成员变量(属性)
	DataType* _array;
	size_t _capacity;
	size_t _size;
};

/*
//  c语言堆栈初始化; 方法和属性是分开的,因此初始化时,需要将定义的栈的地址进行传参
void StackInit(struct Stack* ps, size_t capacity)
{
	ps->_array = (DataType*)malloc(sizeof(DataType)* capacity);
	// ..
}
*/

// c++兼容c语言struct的用法
typedef struct Queue
{
	// ...
}Q;

void QueueInit(Q* pq)
{}
void QueuePush(Q* pq, int x);
{}

// C++
struct ListNode
{
    // 此处不用写为struct ListNode* next; 因为ListNode 是类名,是c++的用法
	ListNode* next;   
};

int main()
{
	Stack s;     // stack是类名,   s为创建的对象
	s.Init(10);
	s.Push(1);
	s.Push(2);
	s.Push(3);
	cout << s.Top() << endl;
	s.Destroy();
	
	// 兼容C语言中对结构体的用法
	Q q1;
	struct Queue q2;
	QueueInit(&q1);

	// C++
    // 用类创建对象;如果是c语言,则必须写为 struct queue q3来创建结构体
	Queue q3; 


	return 0;
}

3.类的定义

class className
{
	// 类体:由成员函数和成员变量组成
    
};  // 一定要注意后面的分号
  • class为定义类的关键字ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略

  • 类体中内容称为类的成员:类中的变量称为类的属性成员变量; 类中的函数称为类的方法或者成员函数

类的两种定义方式:

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

    // c++类的用法
    class Stack // 类型
    {
    public:
    	void Push(int x)
    	{
    		// Init();    // 成员函数的使用顺序是随意的,因为我们需要将整个类看做是一个整体
    		//...
    	}
    
    	void Init(int N = 4)
    	{
    		// ...
    		top = 0;
    		capacity = 0;
    	}
    
    private:
    	int* a;  // 声明
    	int top;
    	int capacity;
    };
    
    // c语言结构体的用法
    struct Stack_C 		// 类型
    {
    	int* a;    		// 声明 
    	int top;
    	int capacity;
    };
    
    int main()
    {
    	// Stack::a = 0;    // a只是声明,并没有分配空间,所以不可以访问
    	// Stack_C::a = 0;  // a只是声明,并没有分配空间,所以不可以访问
    
    	// Stack st;       // 如果想要访问a,需要先创建一个对象
    	// st.a = 0;       // 然后再对a访问,又因为a被private修饰,所以会报错
        
        // stack_c st;    // 先创建一个结构体,所以就可以对a进行访问
        // st.a = 0;      
    
    	return 0;
    }
    
    1. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::
    // 声明和定义分离
    
    // stack.h中放声明
    class Stack // 类型
    {
    public:
    	void Push(int x);
    	void Init(int N = 4);
    private:
    	int* a;  // 声明
    	int top;
    	int capacity;
    };
    
    
    class Queue // 类型
    {
    public:
    	void Init();
    	void Push(int x);
    private:
    	//...
    };
    
    // stack.cpp中,放成员函数的定义
    void Stack::Push(int x)  // 用::(域限定符)来限制成员函数的作用域
    {
    	//...
    }
    
    void Stack::Init(int N)
    {
    	top = 0;
    	capacity = 0;
    }
    
    void Queue::Init()
    {
    
    }
    
    void Queue::Push(int x)
    {
    	//...
    }
    

    注:一般情况下,我们都会使用第二种方式(也就是将声明和定义分开放入.h和.cpp文件中)。

    成员变量命名规则的建议:

    // 看看这个函数,是不是很僵硬?
    class Date
    {
    public:
      void Init(int year)
      {
         // 这里的year到底是成员变量,还是函数形参?
         // 这样命名会让我们混淆
         year = year;
      }
    private:
      int year;
    };
    
    // 所以一般都建议这样
    class Date
    {
    public:
      void Init(int year)
      {
        _year = year;
      }
    private:
      int _year;
    };
    
    // 或者这样
    class Date
    {
    public:
      void Init(int year)
      {
         mYear = year;
      }
    private:
      int mYear;
    };
    
    // 其他方式也可以的,看个人的习惯
    

4.类的访问限定符及封装

4.1 访问限定符

  • C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部用户使用

image-20221209134508007

【访问限定符说明】

  1. public修饰的成员在类外可以直接被访问

  2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)

  3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止

  4. 如果后面没有访问限定符,作用域就到 } 即类结束。

  5. class的默认访问权限为private,struct为public(因为struct要兼容C);默认访问权限也就是用访问限定符说明的情况下

struct Stack
class Stack
{
private:
	int* a;
	int top;
	int capacity;

public:
	void Init(int N = 4)
	{
		// ...
		top = 0;
		capacity = 0;
	}

	void Push(int x)
	{
		//...
	}

};

int main()
{
	Stack st;
	st.Init();
	st.Push(1);
	st.Push(2);
	st.Push(3);
	st.Push(4);

    // st.top;
	// error C2248: “Stack::top”: 无法访问 private 成员(在“Stack”类中声明)

	return 0;
}

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

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

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

注意:在继承和模板参数列表位置,struct和class也有区别,后序给大家介绍。

4.2 封装

  • 面向对象的三大特性:封装、继承、多态

在类和对象阶段,主要是研究类的封装特性,那什么是封装呢?

  • 封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。

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

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

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

5.类的作用域

class Person
{
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender[3];
	int  _age;
};


// 这里需要指定PrintPersonInfo是属于Person这个类域
// 注:使用类的成员函数时,需要指定其作用域
void Person::PrintPersonInfo()
{
   cout << _name << " "<< _gender << " " << _age << endl;
}

6.类的实例化

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

  1. 类是对 对象 进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。

  2. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量

// 例子1
class Person
{
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender[3];
	int  _age;
};


int main()
{
   // 并没有创建对象,_age只是一个声明
   Person._age = 100;   // 编译失败:error C2059: 语法错误:“.”
   return 0;
}
  • Person类是没有空间的,只有Person类实例化出对象,才有具体的年龄

3.做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间

image-20221209155141751

// 例子2
class Person
{
public:
	void PrintPersonInfo()
	{}
    
//private:
	char _name[20];  // 声明
	char _gender[3];
	int  _age;
};


int main()
{
	// 错误示范
	// Person::_age = 1;
	// Person::PrintPersonInfo();  // this指针

    // 正确使用
	Person p1;
	p1._age = 1;

	Person p2;
	Person* ptr = &p2;
	ptr->_age = 2;


	return 0;
}

7.类对象模型

7.1 如何计算类对象的大小

class A 
{
public:
   void PrintA()
   {
      cout<<_a<<endl; 
   }
    
private:
   char _a;
};

问题:类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算一个类的大小?

7.2 类对象的存储方式猜测

  • 对象中包含类的各个成员

image-20221209155452451

缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。那么如何解决呢?

  • 代码只保存一份,在对象中保存存放代码的地址(也就是,每一个对象都可以通过地址,来找到成员函数表)

image-20221209155546076

  • 只保存成员变量,成员函数存放在公共的代码段(成员函数表,再公共区域,每一个对象都可以找得到)

image-20221209155734092

问题:对于上述三种存储方式,那计算机到底是按照那种方式来存储的?

我们再通过对下面的不同对象分别获取大小来分析看下

class A
{
public:
	void PrintA()            
	{
		cout << _a << endl;
	}
    
//private:
	char _a;
	int _b;
};

class A2 
{
public:
	void f2() 
    {}
};

class A3
{};


int main()
{
	A aa1;
	A aa2;
	                              // 成员变量所占内存的大小,按结构体内存对齐的方法来计算
	cout << sizeof(aa1) << endl;  // 打印结果为8

	// 1字节 不存储有效数据,只是用来占位,标识对象存在
    //(并不会将成员函数的地址保存在类里面,而是公共区域)
	cout << sizeof(A2) << endl;   // 打印结果是1
	cout << sizeof(A3) << endl;   // 打印结果是1

	A2 aaa2;  // 只有用1字节占位,才可以区分aaa2,aaaa2这两个对象;如果是0字节,则无法区分
	A2 aaaa2;
	cout << &aaa2 << endl;   
	cout << &aaaa2 << endl;

	aa1._a = 'A';    // aa1和aa2都调用的是相同的成员函数,因此,第三种计算成员函数的方法最优
	aa1.PrintA();   // 打印结果是 A

	aa2._a = 'a';
	aa2.PrintA();  // 打印结果是 a


	return 0;
}

结论:一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。

7.3 结构体内存对齐规则

  1. 第一个成员在与结构体偏移量为0的地址处。

  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的对齐数为8

  1. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。

  2. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是最大对齐数(含嵌套结构体的对齐数)的整数倍

8.this指针

8.1 this指针的引出

#include<iostream>
using namespace std;

// 日期类
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;
	d1.Init(2022, 9, 21);

	Date d2;
	d2.Init(2022, 9, 22);

	d1.Print();  
	d2.Print();

	return 0;
}

//打印结果为
// d1.Print();  2022-9-21
// d2.Print();  2022-9-22

对于上述类,有这样的一个问题:

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

  • C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成
#include<iostream>
using namespace std;

class Date 
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	// 但是我们可以在类里面用this指针
	void Print()
	{
		//  this指针的类型:类的类型* const(如日期类 Date* const this;Date就是类的类型),即成员函数中,不能给this指针赋值。
        // 错误示范:this = nullptr;  this被const修饰,因此不可以被修改
		cout << this << endl;
        
        // 初学者可以自己加上this指针,熟练之后可以不用加,可以部分加,也可以全部加
		//cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	// Print(&d1) // 这种方法是不可取的
	// Print(&d2)
	// this指针的定义和传递,都是编译器的活,我们不能去抢
	// 但是我们可以在类里面用this指针
	
    /*void Print(Date* this)  // 这种也是不可取的
	{
	cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
	}*/

private:
	int _year;     // 年
	int _month;    // 月
	int _day;      // 日
};

int main()
{
	Date d1;
	d1.Init(2022, 9, 21);

	Date d2;
	d2.Init(2022, 9, 22);

	d1.Print();
	d2.Print();

	cout << &d1 << endl;
	cout << &d2 << endl;

	return 0;
}

// 打印结果
// 0133FA18
// 2022-9-21
// 0133FA04
// 2022-9-22

// 0133FA18    d1的地址
// 0133FA04    d2的地址
//  通过打印结果可以验证 &d1传递给对应的this指针
//  通过打印结果可以验证 &d2传递给对应的this指针

8.2 this指针的特性

  1. this指针的类型:this被const修饰,即成员函数中,不能给this指针赋值。

  2. 只能在“成员函数”的内部使用

  3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针

    4.this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

image-20221209165304024

【面试题】

  1. this指针存在哪里?

答:this指针为形参,一般存在栈帧中(vs下面进行优化,使用exc寄存器传递)

  1. this指针可以为空吗?
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
	void Print()
	{
		cout << "Print()" << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = nullptr;
    
	p->Print();  
	return 0;
}
// 2.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
private:
	int _a;
};

int main()
{
	A* p = nullptr;
	p->PrintA();
	return 0;
}

image-20221209170345152

8.3. C语言和C++实现Stack的对比

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结构体变量的地址

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

  1. 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);    // push(&s, 1)   传递过去之后,&s,被this指针接收
	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语言中需用用户自己维护。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(三)LTspice学习交流分析

文章目录 前言一、Edit simulation cmd二、添加激励总结 前言 上一节我们学习了LTspice的安装&#xff0c;很简单&#xff0c;无脑安装 &#xff08;一&#xff09;LTspice简介 &#xff08;二&#xff09;LTspice学习之简介2 今天我们来学习一下LTspice另一个非常重要的仿真功…

血泪教训!程序员副业接单做私活避坑指南!!!

缘起 经常有粉丝朋友向我哭诉留言&#xff0c;告知大白自己兼职被骗的经历&#xff1a; 在大家找兼职踩坑过程中&#xff0c;我总结下来就是以下几点血泪教训&#xff1a; 1. 有没有靠谱兼职推荐&#xff1f; 2. 零基础怎么兼职接单&#xff1f; 3. 怎么渗透&#xff1f;怎么…

C++设计模式:桥模式(五)

1、定义与动机 桥模式定义&#xff1a;将抽象部分&#xff08;业务功能&#xff09;与实现部分&#xff08;平台实现&#xff09;分离&#xff0c;使他们可以独立地变化引入动机&#xff1a; 由于某些类型的固有的实现逻辑&#xff0c;使得它们具有两个变化的维度&#xff0c;…

2.k8s架构

目录 k8s集群架构 控制平面 kube-apiserver kube-scheduler etcd kube-controller-manager node 组件 kubelet kube-proxy 容器运行时&#xff08;Container Runtime&#xff09; cloud-controller-manager 相关概念 k8s集群架构 一个Kubernetes集群至少包含一个控制…

飞书API(3):Python 自动读取多维表所有分页数据的三种方法

上一小节介绍了怎么使用 Python 读取多维表的数据&#xff0c;看似可以成功获取到了所有的数据&#xff0c;但是在实际生产使用过程中&#xff0c;我们会发现&#xff0c;上一小节的代码并不能获取到所有的多维表数据&#xff0c;它只能获取一页&#xff0c;默认是第一页。因为…

MySQL的存储引擎、索引与事务

常见的端口号 MySQL–3306http–80https–443tcp–23fcp–21tomcat–8080ssh–22oracle–1521rockermq–9876 存储引擎 使用指令查看所有引擎&#xff1a; show engines;从图中可以看出MySQL默认的存储引擎是InnoDB&#xff1b;并且在5.7版本所有的存储引擎中只有 InnoDB 是…

亮数据----教你轻松获取数据

文章目录 1. 数据采集遇到的瓶颈1.1 不会造数据&#xff1f;1.2 不会写爬虫代码&#xff1f; 2.IP 代理基础知识2.1 基本概念2.2 作用和分类2.3 IP 代理的配置和使用2.4 安全和合规 3. 为何使用亮数据 IP 代理3.1 拥有丰富的代理网络服务3.2 简单易操作的采集工具3.3 拥有各平台…

路由器对数据包的处理过程分析笔记

虽然TCP-IP协议中传输数据会在各个路由器再次经过物理层、链路层、网络层的解封装、加工、封装、转发&#xff0c;但是对于两个主机间的运输层&#xff0c;在逻辑上&#xff0c;应用进程是直接通信的。 路由器主要工作在网络层&#xff0c;但它也涉及到物理层和链路层的一些功能…

PWM 脉宽跟随方案介绍

1. 前言 数字电源产品在使用桥式电路拓扑或是多路交错控制中&#xff0c;有时会需要滞后臂的 PWM 脉宽严格跟随超前臂的 PWM 脉宽&#xff0c;或从路的 PWM 脉宽严格跟随主路的 PWM 脉宽&#xff0c;本文将介绍如何利用高精度定时器实现 PWM 输出脉宽跟随&#xff0c;一种使用…

ai智能电销机器人的核心技术,工作原理和作用

科技快速发展的同时&#xff0c;带来了人工智能产品的普及。而ai智能电销机器人则成为推进电销行业的产物&#xff0c;那么ai智能电销机器人是如何帮助企业高效触客&#xff0c;有效地工作&#xff0c;效果又如何呢&#xff1f;我们一起来看看吧&#xff01; 一、ai智能电销机器…

软件的生命周期_瀑布模型

瀑布模型 描述软件生成到消亡的过程模型图 该模型目前实际工作中已不常用&#xff0c;但是该模型是其他新型模型的“鼻祖 瀑布模型的优点 每个阶段比较清楚&#xff0c;并且有对应的文档产生 当前一个阶段完成后&#xff0c;才开始后面的阶段&#xff08;一次性的&#xff09…

「媒体邀约」天津媒体邀约资源有哪些?媒体宣传现场报道

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 天津作为中国北方的重要城市&#xff0c;拥有丰富的媒体资源&#xff0c;可以为各类活动提供全面的媒体宣传和现场报道。以下是天津地区的媒体邀约资源&#xff1a; 1. 报纸媒体 - 《天…

如何搭建APP分发平台分发平台搭建教程

搭建一个APP分发平台可以帮助开发者更好地分发和管理他们的应用程序。下面是一个简要的教程&#xff0c;介绍如何搭建一个APP分发平台。 1.确定需求和功能&#xff1a;首先&#xff0c;确定你的APP分发平台的需求和功能。考虑以下几个方面&#xff1a; 用户注册和登录&#xff…

Redis从入门到精通(九)Redis实战(六)基于Redis队列实现异步秒杀下单

文章目录 前言4.5 分布式锁-Redisson4.5.4 Redission锁重试4.5.5 WatchDog机制4.5.5 MutiLock原理 4.6 秒杀优化4.6.1 优化方案4.6.2 完成秒杀优化 4.7 Redis消息队列4.7.1 基于List实现消息队列4.7.2 基于PubSub的消息队列4.7.3 基于Stream的消息队列4.7.4 基于Stream的消息队…

DIY可视化UniApp表格组件

表格组件在移动端的用处非常广泛&#xff0c;特别是在那些需要展示结构化数据、进行比较分析或提供详细信息的场景中。数据展示与整理&#xff1a;表格是展示结构化数据的理想方式&#xff0c;特别是在需要展示多列和多行数据时。通过表格&#xff0c;用户可以轻松浏览和理解数…

Spring Boot-02-依赖管理和自动配置

二、Spring Boot的两大重要机制 1. 依赖管理机制 开发什么场景&#xff0c;导入什么场景启动器&#xff0c;场景启动器自动把这个场景的所有核心依赖全部导入进来。maven依赖传递原则&#xff1a;A依赖B&#xff0c;B依赖C&#xff0c;则A就拥有B和C。每个boot项目都有一个父…

防火墙配置IPSec VPN【IPSecVPN概念及详细讲解】

防火墙配置IPSecVPN-点到点 配置目标 总公司内网与分公司内网互通 拓扑 配置ISP路由器 <Huawei>u t m <Huawei>sys [Huawei]sys ISP [ISP]interface g0/0/0 [ISP-GigabitEthernet0/0/0]ip address 100.1.1.102 24 [ISP-GigabitEthernet0/0/0]q [ISP]interface g…

【cocos creator】【编辑器插件】cocos creator文件复制时,解决cocos creator uuid冲突

&#xff01;&#xff01;&#xff01;修改前先备份 1、将文件夹放在packages文件夹下 2、打开项目&#xff0c;选择要刷新uuid的文件夹 3、菜单栏点击 扩展->refresh-uuid 4、等控制台提示&#xff1a;资源uuid刷新完成&#xff0c;重启项目&#xff08;&#xff01;&#…

VSCODE自动更新无法连接远程服务器报错“waiting for server log...“的解决方法

问题描述 一觉醒来打开vscode发现连接远程服务器显示无法连接&#xff0c;终端一直报错“waiting for server log…"&#xff0c;经查是因为vscode自动更新到了1.86&#xff0c;对于远程服务器的linux版本要求较高。这里记录下解决方法。 解决方法 1. 下载vscode便携版…

网络安全之命令注入

漏洞原理&#xff1a; 应用系统设计需要给用户提供指定的远程命令操作的接口&#xff0c;比如&#xff1a;路由器&#xff0c;防火墙&#xff0c;入侵检测等设备的web管理界面。一般会给用户提供一个ping操作的web界面 用户从web界面输入目标IP&#xff0c;提交后台会对改IP地…