C++ 类和对象(中)构造函数 和 析构函数 ,const成员

news2024/11/25 9:48:44

 上篇链接:C++ 类和对象(上)_chihiro1122的博客-CSDN博客

类的6个默认成员函数

 我们在C当中,在写一些函数的时候,比如在栈的例子:

如上述例子,用C++ 返回这个栈是否为空,直接返回的话,这栈空间没有被释放,就会内存泄漏,如果我们直接释放这个栈,那么我们就拿不到这个栈是否为空的 bool 类型值。那么我们在C中就是用 一个 bool 类型变量来接收这函数返回的布尔值 ,然后再释放,最后返回这个 bool 类型变量的值。

这样做非常的麻烦,C++的祖师爷肯定也想到的这些,这就有了类当中的  6 个默认成员函数。

 我们在 上篇中提到了 空类,那么空类当中真的是什么都不存储吗,并不是。其实在空类当中,编译器会帮我们自动的去创造  6  个默认成员函数:

 其中的 构造函数和析构函数就可以帮我们自动进行 一些 如上述的初始化和 销毁的操作。

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

 构造函数

构造函数是一个特殊的函数,它的名字和类的名字相同而且在创建类的类型对象的时候调用,一保证每一个成员都有一个初始值,并且,构造函数在对象的整个生命周期当中值调用一次

 特性

 需要注意的是,虽然构造函数是帮助对象当中的成员初始化,但是仅仅只是初始化,并不是给这个成员创建空间。

 其特性如下:

  • 构造函数名和类名一样
  • 没有返回值,也不需要 如 void 这样规定返回值类型
  • 实例化对象时编译器会自动的调用构造函数
  • 构造函数可以重载,也就是说我们可以创建多个构造函数。

 定义构造函数

 我们以上篇中的 栈的初始化来举例子,如果我们要用函数来实现栈的初始化的话,应该这样写:

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;
}

如果我们用构造函数来实现话,就这样来实现:

class Stack
{
 public:
    Stack(int capacity = 4)
    {
            _array = (DataType*)malloc(sizeof(DataType) * 3);
            if (NULL == _array)
        {
            perror("malloc申请空间失败!!!");
            return;
        }
            _capacity = 3;
            _size = 0;
    }
};    

 我们就可以直接把在C++当中 实现的 初始化函数 copy 在这个构造函数中。

这个构造函数在创建 对象的时候就可以自动的去调用,也就是说,可以帮我们在创建对象的时候自动的去 初始化栈。这样我们在使用 这个栈的时候就不需要再去初始化一遍栈了。

 而且一般构造函数都是public的,如果是private 私有的就会报错:

当然我们上述说的是一般,也就是说不是必须的,构造函数也可以是 私有的,但是这是比较 高级的玩法。

如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦
用户显式定义编译器将不再生成。

 如果我们在类当中没有 定义 构造函数,那么编译器自动的的给我们创建一个 无参数的默认构造函数。

   在C++当中分为基本类型 / 内置类型,这些是语言本身定义的类型,如  int / double / char / 指针;自定义类型,如 用 class / struct 等等来创建的类型。

如果我们不写构造函数的定义,编译器会默认生成构造函数,内置类型不做处理,自定义类型会去调用他的默认构造:

class A
{
public:

	void Print()
	{
		cout << _a << " >>" << _b << " >> " << _c << endl;
	}

protected:
	int _a;
	int _b;
	int _c;
};

int main()
{
	A a;
	a.Print();

	return 0;
}

 输出:

 我们发现没有初始化为0,这是在C++当中的语法规定的,但是现在有些新的编译器就会把这个成员默认值初始化为 0。 像VS2019 在类当中 如果在类当中定义了自定义类型的成员,那么其中的内置类型和自定义类型都会进行处理。但是 在 VS2013  就会处理。

 也就是说,内置类型处不处理是编译器个性化的行为,但是在C++当中是规定 内置类型是不进行处理的,我们要默认认为他是不处理的。

 所以,让编译器自己生成构造函数这种操作我们是不建议的,因为指不定这个编译器既不会对 内置类型进行 初始化0,所以,如果类当中有内置成员,我们就要自己实现构造函数来初始化 类当中的 内置成员。

 如果全部都是自定义类型的成员,可以考虑让编译器自己生成。

 类当中的成员的缺省值

 注意:在C++11 当中,C++的开发团队也觉得,内置成员不处理,自定义成员处理,这样当时不妥当,所以大了一个补丁,不是把之前的语法改了,让函数在声明的时候可以给缺省值。

class A
{
public:

	void Print()
	{
		cout << _a << " >>" << _b << " >> " << _c << endl;
	}

protected:
	int _a = 1;
	int _b = 1;
	int _c = 1;
};

int main()
{
	A a;
	a.Print();

	return 0;
}

 输出:

我们发现上述就输出的是我们 缺省值。注意:此处不是初始化,是在声明的时候给的 缺省值。

 这里的 成员的 缺省值 是给编译器默认的 构造函数用的。

 因为是编译器默认使用 构造函数生成的缺省值,所以当我们在 类当中实现了 构造函数,这个缺省值就没有用了:

class A
{
public:

	A(int a, int b, int c)
	{
		_a = a;
		_b = b;
		_c = c;
	}

	void Print()
	{
		cout << _a << " >>" << _b << " >> " << _c << endl;
	}

protected:
	int _a = 1;
	int _b = 1;
	int _c = 1;
};

int main()
{
	A a(9,9,9);
	a.Print();

	return 0;
}

 输出:

 此时我们Print()函数就输出的是, 9  >>  9  >>  9 ,不是 1  >>  1  >>  1  了。

 构造函数重载需要注意的点

class A
{
public:

	A()
	{
		_a = 2;
		_b = 2;
		_c = 2;
	}

	A(int a, int b, int c)
	{
		_a = a;
		_b = b;
		_c = c;
	}

	void Print()
	{
		cout << _a << " >>" << _b << " >> " << _c << endl;
	}

protected:
	int _a = 1;
	int _b = 1;
	int _c = 1;
};

 如上述的两个构造函数就构成了函数重载,构造函数的形参,同样是可以有缺省参数的:

	A(int a = 0, int b = 0, int c = 0)
	{
		_a = a;
		_b = b;
		_c = c;
	}

 但是,构造函数中使用缺省参数会出现一些问题,如这个例子:

	A()
	{
		_a = 2;
		_b = 2;
		_c = 2;
	}

	A(int a = 0, int b = 0, int c = 0)
	{
		_a = a;
		_b = b;
		_c = c;
	}

 比如这例子,在语法上是支持的,也就是说,上述两个构造函数是支持语法的,但是在调用无参数的构造函数的时候会出现一些问题:

 我们发现此时编译器就对该调用哪一个构造函数产生了歧义。

 当然我也不能  " A a1(); " 这样写,这样写与函数声明冲突,我们在下面会讲解。

 当然,我们给一个,或者两个参数都是可以的 ,因为他不会和 无参数的构造函数冲突。

所以,我们总结一下, 其实无参数构造函数和  全缺省 构造函数都属于是 默认构造函数,再加上 我们不写编译器就会生成的 默认构造函数,这三者只能有一个,因为这三者在外部的调用都是一样的编译器不能识别。

 构造函数的调用
 

 构造函数的调用跟普通函数也不太一样,普通函数代用是函数名 + 参数列表构造函数调用是对象名 + 参数列表。

 像上述的例子:


int main()
{
	A a2(9,9,9);
	a2.Print();

	return 0;
}

 在类当中定义的 构造函数 是 以 类名为函数名来创建的,但是调用的时候,使用的是 创建的对象的名字。

 当这个构造函数没有参数的时候,如下:

	A()
	{
		_a = 2;
		_b = 2;
		_c = 2;
	}

 那么,就直接创建这个对象,不需要加 () 参数列表,都会自己调用 类当中  不带参数的 构造函数,如果这个例子:
 

class A
{
public:

	A()
	{
		_a = 2;
		_b = 2;
		_c = 2;
	}

	A(int a, int b, int c)
	{
		_a = a;
		_b = b;
		_c = c;
	}

	void Print()
	{
		cout << _a << " >>" << _b << " >> " << _c << endl;
	}

protected:
	int _a = 1;
	int _b = 1;
	int _c = 1;
};

int main()
{
	A a1;
	a1.Print();

	return 0;
}

 输出:

 我们发现此时输出的是,无参数 构造函数 当中初始化的值。

 注意:有参数是需要加 "()" 在 "()" 当中传参,而如果我们想调用 无参数的构造函数,那么我们是不能 加  "()"  的,如果加  "()" 就会报错:

 这是因为,如果这样写,会跟函数声明冲突,编译器不好识别。

 如上述的   " A a1(); "  是不是和 不带参数的 函数的 声明是一样,这样编译器就不好识别;但是如果是 带参数的 ,如:  " A a2(9,9,9); "  这样的就不会报错了,因为这样编译器就可识别了,因为我们在声明有参数函数的时候,参数是要加 参数的类型的: " void func ( int a, int b , int c);  " 这样的。而我们在调用有参数的构造函数的时候,是函数的传参,不需要 写 参数的类型。

我们还可以这样来给 对象当中的成员初始化:
 

typedef struct TreeNode
{
	struct TreeNode* left;
	struct TreeNode* right;
	int val;
}TreeNode;

class Tree
{
public:
	Tree(int val)
	{
		_root->left = nullptr;
		_root->right = nullptr;
		_root->val = val;
	}

protected:
	TreeNode* _root = nullptr;
};

int main()
{
	Tree Tr(1);

	return 0;
}

 如上,我们就创建了一个二叉树树结点,这个树结点的左右孩子指针都指向空,并且我们在主函数中创建这个对象的时候,可以个这个树结点当中的val 赋值。

 析构函数

 析构函数和构造函数的作用相反,它并不是完全的对  对象 进行销毁,局部对象进行销毁是由编译器来执行。所谓析构函数是对象在销毁的时候,才会执行的操作,来完成对象当中 一些成员的销毁工作。

特性

  •  析构函数的名字和类名相同,只是在最前面加上 "  ~  "   这个符号
  • 没有参数和返回值
  • 一个类只能有一个析构函数,若没有对析构函数进行定义,那么编译器会自动进行创建。注意:析构函数不能进行重载。
  • 在对象的生命周期结束的时候,编译器会自动调用析构函数。

析构函数的定义

如上篇当中的 栈的销毁:

class Stack
{
    ~Stack()
    {
        if (_array)
        {
            free(_array);
            _array = NULL;
            _capacity = 0;
            _size = 0;
        }
    }
}

如上述的~Stack() 函数就是 析构函数,他在 栈的对象销毁的时候就会自动调用。

也就是说,在上篇中提到的,内存泄漏问题,在程序运行的最后,只需要销毁 这个对象就会自动的销毁创建的栈空间。

 有了析构函数和构造函数的时候就不在怕初始化和清理函数了,也简化了。

我们上述也提到了,析构函数不写编译器也会生成默认的 析构函数,那么这个 默认的析构函数和构造函数是一样的,也是内置成员不处理,自定义成员就要按照自定义类型进行处理。

 在比如上述栈的例子,如果类当中都是 自定义类型,那么我们也可以不写析构函数,编译器自动生成的 析构函数就会帮我们 清理销毁 对象空间。

 

 拷贝构造函数

 当我们像通过某个对象当中的成员的值,在实现某些功能,但是实现过程有需要修改对象成员里的值,我们又不想修改这个对象当中成员的值,我们就可以创建一个 拷贝构造函数来帮助我们拷贝这个对象当中成员的值。

定义:

 拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存
在的类类型对象创建新对象时由编译器自动调用。

 例子:

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

protected:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date date();  //  创建日期对象
}

如上述例子,我们想把date 这个对象进行拷贝,我们可以在 Data 类当中实现一个 拷贝构造函数;

	Date(const Date& date)
	{
		_year = date._year;
		_month = date._month;
		_day = date._day;
	}

上述就是拷贝构造函数,我们可以发现,这个其实就是构造函数的一种重载,只是这个函数只有一个形参,而且这个形参是 我们需要拷贝 的 对象的 引用,一般我们在这个函数中,不想去修改外部的我们 拷贝的对象,所以这个引用是 const 不允许 修改的。在构造函数中对成员的值的修改也容易搞混,容易写反,写入const 也能防止这样的事情发生。

 注意:一下代码是编译报错的;

 我们发现,我们在构造函数的创建的时候,他不给我们创建 本类 类型的参数的构造函数的重载函数,我们只能在此传入 本类 类型的 引用:

 这是因为,在C++当中规定:在函数传参的时候,如果是内置类型就是直接拷贝;如果是自定义类型就必须先调用 拷贝构造函数 之后去完成拷贝。

 如下图:

 这个例子,当调用 func(1) 时候就是直接拷贝,当调用 func(d1) 这个函数的时候,我们来调试来看看过程:

 此时程序运行到 func(d1) 函数这一行,当我们 按下 F11 (或者是 fn + f11  看电脑) 单步执行程序,执行下一步时,我们发现,不是直接跳到 func(Date d1) 函数这一行,而是直接跳到 Date(const Date date) 拷贝构造函数这一行了:

 这也就印证了,在函数传入 自定义类型的时候,先是传参,自定义类型传参就要先 调用拷贝构造函数。

 当我们在按下 f11 才进入到 func(Date date)这个函数。

那么,如果自定义类型传参是按照上述过程来实施的,那么如果我们在构造函数中,直接像下图一样写,那么就会出现无限函数递归的可怕结果:

 其过程也不难理解,如下图所示:

 

 那么,如果我们在传参的时候,传的不是自定义类型,传入的自定义类型的引用,他是对象的一个别名,在传参的时候都不会开空间,那么他就不会调用拷贝构造函数。

 若未显式定义,编译器会生成默认的拷贝构造函数

 如果我们在类当中没有写 拷贝构造函数,那么编译器会自动给我们生成 拷贝构造函数。

此处默认的 拷贝构造函数 ,对于其中不同类型成员的拷贝是这样的:

  • 对于内置类型成员进行值拷贝/浅拷贝。
  • 对于自定义类型则会调用它的构造拷贝函数。

 所以实际上,对于简单的对象的拷贝,默认的拷贝构造函数是可以自动来完成的,如下例子:

但是对于像栈,队列等等这些 对象当中动态开辟空间了的对象,那么默认的就不能拷贝成功了。 

 比如我们拷贝栈,栈当中存储了,动态开辟空间的空间的指针,top栈当中存储的有效数据的位置。那么如果我们使用默认 拷贝构造函数,只能拷贝上述两个 变量,不能开辟空间,那么函数当中拷贝的对象,和外部主函数中的对象,两个对象将使用同一块空间:

 这样就违背了我们使用 拷贝构造函数的初衷。

而且,假设我们不在使用这两个对象的时候,就会调用对象的析构函数,来销毁栈空间,那么当其中一个对象调用析构函数销毁了空间之后,另一个对象再次销毁就会有问题。在两个操作之间没有对这个空间进行重新的声明,同一块空间不能销毁两次。

 

 比如这个例子,后定义的会先析构,那么st1 这个对象在析构的时候就有问题了。

 而且,这只是其中之一个问题,我们以后再 push  pop 等等操作都会有问题,两个对象使用的是一块空间,任何操作修改了其中以对象当中空间,都会影响另一个对象。

所以我们就要自己创建 拷贝构造函数,来拷贝其中的 栈空间,如下例子,我们采用深拷贝:

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_array = (int*)malloc(sizeof(int) * 3);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = 3;
		_top = 0;
	}

	Stack(const Stack& st)
	{
		_array = (int*)malloc(sizeof(int) * _capacity);
		if (_array == NULL)
		{
			perror("malloc fail");
			return;
		}

		memcpy(_array, st._array, sizeof(int) * _capacity);
		_top = st._top;
		_capacity = st._capacity;
	}
	
protected:
	int* _array = nullptr;
	int _capacity = 0;
	int _top = 0;

};

 如上就是是 Stack的构造函数  和 拷贝构造函数的创建方式,上述采用的是深拷贝。

 至此,我们就明白,为什么祖师爷在自定义类型的传参的时候,为什么不直接值传递,不直接把值传进来了。

因为,就像我们上述说的,如果我们直接把 值 传入,那么会导致,两个对象使用的是一个栈空间。

 所以拷贝构造是为深拷贝而生的,值传递都是使用 C++ 当中 默认 生成的 拷贝构造函数就可以实现了。

那么在函数的传参和 返回的时候,如果返回的是自定义类型,那么再传参的时候会 先去调用 这个自定义类型的 拷贝构造函数;在 函数返回的时候,会创建一个临时变量来暂时存储 这个返回值,这个临时变量的大小也是根据 自定义类型大小来的定的;如果这个自定义类型当中 的数据量很大,比如栈空间中存储了 10000 个数据,那么上述两种情况,我们都要先 拷贝 ,那么这个拷贝是消耗空间和时间的,我们程序的效率会下降。

像上述的两种情况,在数据量很大的情况是下是非常明显的。

所以一般情况下,我们能使用引用来进行 传参和 返回 尽量使用引用来 传参和返回。

 但是我们在使用引用传参和引用返回的时候,需要注意是,我们要用空间是否还存在有没有被销毁:

Stack& func()
{
	static Stack st;
	return st;
}

比如这种情况是可以返回的,因为 创建的 st 是静态的,函数执行完,函数栈帧销毁,他不会销毁。

Stack& func()
{
	 Stack st;
	return st;
}

像这个例子,st 就不会静态的,也不是全局的,是函数中的局部变量,那么函数执行完之后,他就会销毁,那么此时我们在返回这个已经销毁了的空间的引用,就会很危险:

Stack& func()
{
	 Stack st;
	return st;
}

int main()
{
	printf("-------\n");
	Stack st = func();
	printf("-------\n");

	return 0;
}

比如这个例子,在 main 函数中,我们使用 st  来接收,此时的st 就作为是 func ()函数中 st 的引用(别名) ,那么func()函数中的st 在函数销毁的时候就已经销毁了,那么我们在main函数中创建的 st 栈也要进行销毁,销毁之后,自动创建的析构函数会这个指针给置空,那么我们在main函数中的 st  也要进行释放,销毁,对空指针的销毁就会出现问题。

const成员

 

 如这个例子,第一个可以调用,但是第二个就不能调用了。

如下图所示:

 第二中种中的传入的指针是const 的,但是在 Print()函数中是 Date* 的,这是权限的放大,当然就不能使用。而第一种是权限的平移,就可以使用。

那么像上述的 const Date d2() 这种如何调用其中的成员函数呢?

那么我们就考虑把 Print() 当中的 this 指针 变成 const 的,这样第一种情况就是权限的缩小,而第二种是权限的平移,我们就可以调用了。

但是 this 是 隐含的,我们不能显示的去写,所以祖师爷想了一个位置,就是在 成员函数定义的时候,在()后加一个 const 代表 this 指针是 const 的。这个const 修饰的是 *this。既然修饰的是*this,那么就代表这个 this 指针指向的内容不能被修改。

 

当我们在成员函数后面加上 const 修饰 this 指针之后,在外的无论是 普通的对象还是 const 修饰的对象都可以调用。

 问题:我们发现上述 在成员函数 之后 加上 const 修饰很香,那么是不是 所有的成员函数都 加 const 修饰呢?

 答案是否定的,我们上述 Print() 成员函数只是打印 对象当中的成员的值,没有对 对象当中的成员进行修改,但是如果这个成员函数是 需要修改 成员属性的,那么这个函数就不能 在函数后面加 const修饰。

 比如上述实现的 += 重载函数,上述的 _day  是Date类当中的成员,而上述的 _day += day ;这个代码会被编译器 转化为  this->_day += day;  但是 *this 是被 const 修饰的,意思就是说,this指针指向的内容不能被修改,所以我们发现,当我们加了 const 之后,上述代码编译报错了。

其实这里就是解决 权限的放大缩小平移的问题,如下例子:

 这个在Date类当中重载的成员函数:

Date d1(2019,2,2);
const Date d2(2019,3,3);

d1 < d2;  // 1
d2 < d1;  // 2

 上述代码 1 能编译通过,但是 代码2 不能编译通过。代码2 的 d2 在传参的时候其实就是权限的放大,所以不会编译通过。

而当我们 把这个 < 重载函数 后加一个 const ;代码2 就可以编译通过了。

而,向上述的,其中没有修改 成员 属性的成员函数,我们都建议加上 const 修饰,如果成员函数中修改了 对象当中的成员属性,那么就不能加 const 修饰。

 理解const修饰词

	const int a = 10;
	int b = a; // 正确

	int& c = a; //错误

 第一种不涉及权限的放大和缩小,因为这里只是一个拷贝,只是把 a 的值拷贝给 b,在拷贝之后,b 的改变不影响 a ;而第二种c 是a 的别名,c  的改变会改变 a  ,这里就设计权限的放大。

 

 要想引用 c 也是不能改变的,此时是权限的平移。

那么指针也是和 引用类似的,如果按照上述的错误操作,也是权限的放大,会报错:

  •  1. const对象可以调用非const成员函数吗?
  • 2. 非const对象可以调用const成员函数吗?
  • 3. const成员函数内可以调用其它的非const成员函数吗?
  • 4. 非const成员函数内可以调用其它的const成员函数吗?

 取地址及const取地址操作符重载

Date* operator&()  // 普通对象的取地址重载函数
{
    return this;
}
const Date* operator&()const  // const 对象的取地址重载函数
{
    return this;
}

其他的操作符,对于自定义类型该如何实现具体功能,编译器是无法判断的,但是取地址不同,他只需要返回这个对象的地址就行了。所以我, 使用编译器默认生成的 取地址 重载函数也可以实现取地址功能。

除非你不想要别人取到 这个 普通对象的地址,那么就直接返回 nullptr。

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

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

相关文章

Selenium+Pytest自动化测试框架实战

前言 1、Selenium是一个免费的(开源)自动化测试组件&#xff0c;适用于跨不同浏览器和平台的Web应用程序【selenium是一个自动化工具&#xff0c;而不是自动化框架】。它非常类似于HP Quick Test Pro (QTP现在叫UFT)&#xff0c;只是Selenium侧重于自动化基于Web的应用程序。使…

初、高中生到底该不该学习编程?

从小学&#xff0c;到初中&#xff0c;再到高中&#xff0c;知识的提升主要体现在一个方面上&#xff0c;就是知识越来越抽象了。很多孩子在初中成绩还可以&#xff0c;到了高中就跟不上了&#xff0c;这是最主要的一个原因。 编程主要就是要求学习它的人&#xff0c;有较强的…

2023武生院计科专升本指南

自我介绍一下&#xff0c;我叫啊超&#xff0c;22级专升本上岸武生院考了211的计应学长&#xff0c;社恐&#xff0c;不善言辞&#xff0c;出门都走下水道&#xff0c;吃饭因社恐&#xff0c;屡次不敢买单。单身可撩&#xff0c;哈哈哈~ 我只是提出自己的一些个人建议&#xff…

utittest和pytest中mock的使用详细介绍

Mock是Python中一个用于支持单元测试的库&#xff0c;它的主要功能是使用mock对象替代掉指定的Python对象&#xff0c;以达到模拟对象的行为。 python3.3 以前&#xff0c;mock是第三方库&#xff0c;需要安装之后才能使用。python3.3之后&#xff0c;mock作为标准库内置到 un…

防火墙(firewall)

前言 计算机的安全性历来就是人们热衷的话题之一。而随着Internet的广泛应用&#xff0c;人们在扩展了获取和发布能力的同时也带来信息被污染和破坏的危险。这些安全问题主要是由网络的开放性、无边界性、自由性造成的&#xff0c;还包括以下一些因素。 1. 计算机操作系统本身…

分布式补充技术 01.AOP技术

01.AOP技术是对于面向对象编程&#xff08;OOP&#xff09;的补充。是按照OCP原则进行的编写&#xff0c;(ocp是修改模块权限不行&#xff0c;扩充可以) 02.写一个例子&#xff1a; 创建一个新的java项目&#xff0c;在main主启动类中&#xff0c;写如下代码。 package com.co…

CTSI 基金会储备透明度报告——2023 年 1 月

由于 Cartesi 代币 (CTSI) 的下一次解锁定于 2023 年 1 月 23 日&#xff0c;我们很高兴接着上一份透明度报告&#xff0c;我们本次依旧提供关于 Cartesi 基金会的治理、运营以及 CTSI 代币如何分配的另一份官方透明度报告。 提醒一下&#xff0c;为了保证诚实和可信度&#xf…

软件测试工程师简历项目经验怎么写

软件测试工程师简历项目经验怎么写 面试是我们进入一个公司的门槛&#xff0c;通过了面试才能进入公司&#xff0c;你的面试结果和你的薪资是息息相关的。 在面试之前&#xff0c;不得不聊聊简历&#xff0c;简历是职场的敲门砖&#xff0c;是拿到offer的通行证&#xff0c;那么…

【unity学习记录-场景绘制+物体碰撞,场景物体的层级关系】跟着Unity2D官方入门教程 Ruby‘ Adventure

文章目录 创建tilemap编辑一下资源&#xff0c;瓦片调色对于瓦片没有填满的情况&#xff0c;调整每片瓦片的像素 添加点小树或其他&#xff0c;调整层级给树木增加一些碰撞的效果&#xff0c;调整碰撞范围&#xff0c;角色也要添加刚体人物倒着走路的解决方法&#xff08;解决角…

线程与进程,你真得理解了吗

线程与进程&#xff0c;你真得理解了吗 1 进程与线程的关系和区别2 并行与并发3 线程共享了进程哪些资源 相信大家面试时一定没少被一个问题刁难&#xff0c;那就是进程和线程的区别是什么&#xff1f;这个问题延申开来并不像表面那么简单&#xff0c;今天就来深入一探。 开始…

个人简历html网页代码(使用chatgpt完成web开发课的实验)

使用chatgpt完成web开发课的实验 前提&#xff1a; chatgpt的使用&#xff0c;建议看https://juejin.cn/post/7198097078005841980或者自己随便找 要学会用“出国旅游”软件 vscode的基本使用 炼丹开始&#xff1a; 炼丹材料&#xff1a; 帮我写一个html页面&#xff0c;内…

沁恒 CH32V208(四): CH32V208 网络DHCP示例代码分析

目录 沁恒 CH32V208(一): CH32V208WBU6 评估板上手报告和Win10环境配置沁恒 CH32V208(二): CH32V208的储存结构, 启动模式和时钟沁恒 CH32V208(三): CH32V208 Ubuntu22.04 Makefile VSCode环境配置沁恒 CH32V208(四): CH32V208 网络DHCP示例代码分析 硬件部分 CH32V208WBU6 …

Python 网络爬虫与数据采集(一)

Python 网络爬虫与数据采集 第1章 序章 网络爬虫基础1 爬虫基本概述1.1 爬虫是什么1.2 爬虫可以做什么1.3 爬虫的分类1.4 爬虫的基本流程1.4.1 浏览网页的流程1.4.2 爬虫的基本流程 1.5 爬虫与反爬虫1.5.1 爬虫的攻与防1.5.2 常见的反爬与反反爬 1.6 爬虫的合法性与 robots 协议…

深入理解Java虚拟机:JVM高级特性与最佳实践-总结-3

深入理解Java虚拟机&#xff1a;JVM高级特性与最佳实践-总结-3 垃圾收集器与内存分配策略垃圾收集算法标记-清除算法标记-复制算法标记-整理算法 垃圾收集器与内存分配策略 垃圾收集算法 标记-清除算法 最基础的垃圾收集算法是“标记-清除”&#xff08;Mark-Sweep&#xff…

2023年推荐几款开源或免费的web应用防火墙

2023年推荐几款开源或免费的web应用防火墙 2023年&#xff0c;数字经济将强势崛起&#xff0c;并且成为新一轮经济发展的动力&#xff0c;传统的黑客破坏性攻击如CC&#xff0c;转为更隐蔽的如0day进行APT渗透。所以无论私有服务器还是云厂商如Cloudflare、阿里云、腾讯云等都…

无线安全操作(2)

目录 用户隔离 用户隔离介绍 1、集中式转发方式 2、分布式转发方式 用户隔离配置 用户隔离举例 用户静默排错帮助 ARP抑制 ARP抑制介绍 ARP抑制配置 ARP抑制举例 动态黑名单 动态黑名单概述 动态黑名单配置 动态黑名单举例 动态黑名单排错帮助 无线SAVI 无线…

正月初八,公司复工,我却失业了

昨天&#xff0c;一条吐槽刷屏。 方便大家阅读&#xff0c;原文先贴出来&#xff1a; 正月初八&#xff0c;公司复工&#xff0c;我却失业了。 一大早地铁转公交赶在九点前到达&#xff0c;老板在大门口迎接我们的到来&#xff0c;还发了一个红包&#xff0c;心里暖暖的。 到了…

以太坊EVM源码分析学习记录

EVM 待办清单结构与流程2020年版本的evm结构大致流程 opcodes.gocontract.goanalysis.gostack.gostack_table.goMemory.goMemory_table.goEVM.go区块上下文交易上下文EVM结构以太坊中的调用call、callcode和delegatecall创建合约 interpreter.gojump_table.goinstructions.goga…

极为罕见的大学生现象 凤毛麟角 是参加这种竞赛

大学里有哪些含金量较高&#xff0c;涉及到综合素质加分的竞赛呢&#xff1f; 一定要耐心耐心的看完 对你现在的境地会很有帮助&#xff01;&#xff01;&#xff01; 大学生竞赛大致可以分为综合类和学科类两种。 综合类竞赛面向的范围更大&#xff0c;各个专业的学生均可参…

区块链论文一般发表在哪些地方?

区块链论文一般发表在哪些地方&#xff1f; 区块链论文发表区块链会议区块链会议论文阅读列表区块链相关论文查询论文检索网站 区块链论文发表 会议类&#xff1a; 安全、密码、分布式理论方面的会议&#xff1a;IEEE S&P (Oakland),、ACM CCS,、IACR Crypto、IACR Eurocr…