类和对象 - 下

 

      

 本文已收录至《C++语言》专栏!
作者:ARMCSKGT

      


目录

 

前言

正文

初始化列表

成员变量的定义与初始化

初始化列表的使用

变量定义顺序

explicit关键字

隐式类型转换

自定义类型隐式转换 

explicit 限制转换

关于static

static声明类成员

友元

友元函数

友元函数特殊使用场景

友元类

内部类

概述

特性

匿名对象

编译器对于自定义类型的一些优化

隐式类型转换的优化 

传参优化

返回值优化

说明

合理使用优化

关于对象的理解

最后


 

前言

前面我们介绍了类和对象的基本概念以及类的六个默认成员函数,这些知识已经为我们搭起了一个基本的类框架,不过类和对象中还有一些小细节需要介绍,本节我们将进入类和对象的收尾阶段!


正文

初始化列表


成员变量的定义与初始化

上篇我们学习了构造函数,构造函数是用来初始化成员变量的,而成员变量的定义是在初始化列表,对于一些需要在定义时就赋值的成员,例如 const int x ,这时需要使用初始化列表进行,因为在C++11之前,C++98并不支持在声明列表中给缺省值!

  

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。

    

int num = 0; //在定义时赋初值 - 初始化
num = 1; //被定义后赋值 - 赋值

那么初始化列表与构造函数有什么关系呢?构造函数是对象在实例时就调用的一个函数,初始化列表在构造函数中,会随构造函数一起执行,初始化列表最先执行并将指定的值赋给每个成员变量!如果我们没有显示去写初始化列表编译器仍然会执行初始化列表,只不过内置类型初始化为随机值,自定义类型则调用其默认构造函数

      

示例1:

class Date 
{
public:
    //猜猜下面两个构造函数的区别
    Date() //初始化列表初始化
        :_year(2008)
        ,_month(12)
        ,_day(21)
    {
        _year = 0;
        _month = 0;
        _day = 0;
    }

    Date(size_t year, size_t month, size_t day) //函数内赋值
    { 
        _year = year;
        _month = month;
        _day = day;
    }

private:
    size_t _year;
    size_t _month;
    size_t _day;
};

int main()
{
    Date d1;
    Date d2(1970,1,1);
    return 0;
}
使用初始化列表的构造函数
未使用初始化列表的构造函数

 示例2:

class Test
{
public:
    Test() //a会被初始化为什么?
        :a(1)
    { }
private:
    int a = 2;
};

int main()
{
    Test t;
    return 0;
}
编译器优先使用初始化列表中的初值进行初始化

初始化列表的使用

   

初始化列表:在函数参数()后,函数体前{},以一个(:)冒号开始,接着是一个以(,)逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式,最后一个列表成员后没有任何符号。

//1.所有对象的成员变量都是在初始化列表被定义的
//2.无论是否显示在初始化列表写,每个变量都会在初始化列表中被定义和初始化

class Date
{
public:
    Date(int year = 1970, int month = 1, int day = 1)
        :_year(year) //初始化列表初始化
        , _month(month)
        , _day(day)
    {
        //其他初始化行为    
    }
private:
    int _year;
    int _month;
    int _day;
};

   

注意

  • 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  • 类中包含以下成员,必须放在初始化列表位置进行初始化:引用成员变量,const成员变量,自定义类型成员且该类没有默认构造函数时(简而言之就是需要在初始化指定初始值的成员变量)

   

//错误示例
class Test2
{
    Test2(int a) {}
};

class Test
{
public:
    Test() {}
private:
    const int num; //这三种成员变量不使用初始化列表初始化是无法编译的!
    int& ref;
    Test2 t;
};

int main()
{
    Test t;
    return 0;
}
上述代码编译报错

   

因为构造函数中函数体是以赋值的方式初始化的,在执行赋值语句前,变量需要被定义和初始化,编译器默认的初始化手段是赋随机值,const只能在初始化阶段指定初始值,如果const常亮被赋予随机值是不合理的,这里也突出了构造函数的一些缺陷,为了补足这些缺陷,C++使用初始化列表定义和初始化且初始化列表存在与构造函数中!

       

节选自C++之父Scott Meyers名书Effective C++ ( 其中成员初值列就是初始化列表)

所以尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化!


变量定义顺序

  

成员变量既然在初始化列表定义,那定义的顺序是由什么觉得的?

成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关!

class Test
{
public:
    Test() //猜猜a会被初始化为1吗?
        : b(1)
        , a(b)
    {
        cout << a << endl;
    }
private:
    int a = 0;
    int b = 0;
};

int main()
{
    Test t;
    return 0;
}
a在b前被定义但是b此时未被初始化所以是随机值

结论:成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

 

所以这里建议:初始化列表顺序与成员变量声明顺序一致


explicit关键字


我们在平时写代码中不妨会涉及类型转换,类的实例化也是,实例化对象时也会发生类型转换!


隐式类型转换

对于两个类型不同的变量,如果要赋值则需要进行类型转换。

int i = 0;
double d = 1.23456;
i = d; //这里会发生隐式类型转换


自定义类型隐式转换 

//示例代码
class Test 
{
public:
    //默认构造函数
    Test(int num = 0)
        :_num(num)
    {
        cout << "构造函数" << endl;
    }

    //拷贝构造
    Test(const Test& t)
    {
        _num = t._num;
        cout << "拷贝构造" << endl;
    }

    //赋值重载
    Test& operator=(const Test& t)
    {
        _num = t._num;
        cout << "赋值重载" << endl;
    }

private:
    int _num;
};

int main()
{
    //初始化会成功吗?
    Test t = 10;
    return 0;
}

很显然,初始化成功了,但是我想告诉大家,这次初始化调用了构造函数和拷贝构造和两个函数,进行了隐式类型转换然后初始化了 t 对象! 

实际运行调用

这里大家可能会怀疑,上面解释的明明会调用两个构造函数,但是实际上却只调用了一个构造函数,这里要说明的是:编译器为了优化效率,将10直接对t进行构造(直接构造),这样可以提高效率!

Test t = 10; //这样效率太低了

Test t(10); //编译器优化为直接构造

    

当然如果类初始化时有多参数参与初始化,也支持多参数构造优化!

class Test
{
public:
	Test(int a, int b)
		: _a(a)
		, _b(b)
	{
		cout << "Test()" << endl;
		cout << "_a = " << _a << " " << "_b = " << _b << endl;
	}
private:
	int _a = 0;
	int _b = 0;
};

int main()
{
	Test t = { 1,2 };
	return 0;
}


explicit 限制转换

   

铺垫了这么久,重点来了!

有时候我们不想使构造函数方式类型转换进行构造,可以在构造函数前加上 explicit 关键字禁止类型转换

//示例代码
class Test 
{
public:
    //默认构造函数 - explicit修饰限制类型转换行为
    explicit Test(int num = 0)
        :_num(num)
    {
        cout << "构造函数" << endl;
    }

    //拷贝构造
    Test(const Test& t)
    {
        _num = t._num;
        cout << "拷贝构造" << endl;
    }

    //赋值重载
    Test& operator=(const Test& t)
    {
        _num = t._num;
        cout << "赋值重载" << endl;
    }

private:
    int _num;
};

int main()
{
    //初始化会成功吗?
    Test t = 10;
    return 0;
}
此时类型转换也不存在了,编译器优化也就不存在了

所以,如果我们想要提高代码的可读和规范性,必要时使用explicit修饰构造函数就能防止实例化时发生类型转换!


关于static


在C语言的学习中,我们知道static是静态修饰,被修饰的变量生命周期增长至程序运行周期!但是我们平时在写代码时static必须得慎用,因为其中有很多细节需要我们考虑!


static声明类成员

   

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。

  

特性

  • 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区。
  • 静态成员变量必须在类外定义和初始化且只能初始化一次,定义时不添加static关键字,类中只是声明
  • 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问。
  • 静态成员函数没有隐藏的this指针,不能访问任何非静态成员(这样就不需要实例化对象,只需要类名就能访问)。
  • 静态成员函数只能访问静态成员,因为没有this指针无法调用其他非静态成员,静态成员函数是为了静态成员变量而生的
  • 静态成员也是类的成员,受public、protected、private 访问限定符的限制。

   

//示例
class Test
{
public:
	//Test()
	//	:a(0) 
	//{}
//不能在初始化列表初始化,因为静态成员a不属于任何一个单独的对象,a被所有对象共享
	static int a; //声明为静态成员
};

int Test::a = 0; //类外定义和初始化静态成员 - 类型 类名::静态成员

int main()
{
	++Test::a; //通过类名 Test::a 访问静态成员
	cout << "Test::a : " << Test::a << endl;
	Test t;
	++t.a; //通过对象 t.a 访问静态成员
	cout << "t.a : " << Test::a << endl;

	return 0;
}

  

重点总结: 静态成员不能调用非静态成员,因为没有this指针,但是非静态成员可以调用静态成员,因为静态成员具有全局属性!


由静态成员的特性,可以求出实例化了多少个对象!

因为实例化对象每次都会调用构造函数,我们可以定义一个静态成员计数器num,每次调用构造函数num就加1,最后就可以完成对构造函数调用次数的统计!


class Test
{
public:
	Test() //默认构造
	{
		cout << "Test()  ";
		++num;
	}

	~Test() //析构函数
	{
		cout << "~Test() ";
		cout << "num = " << num << endl; //打印当前num
	}

	static int num;
};

int Test::num = 0;

int main()
{
	for (int i = 0; i < 10; ++i) //循环进行实例对象
	{
		Test t;
	}
	return 0;
}


友元


概述:有些场景下,某些外部函数需要访问类私有和保护成员,友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用

 

友元分为:友元函数和友元类

 

友元声明关键字: friend


友元函数

 

将函数使用friend修饰并声明在类中,则此函数称为该类的友元函数!

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明。

//区别示例
class Test
{
    friend void fun1(const Test& t); //声明为友元函数
public:
    Test(int n = 0)
        :mynum(n)
    {}

private:
    int mynum;
};

void fun1(const Test& t)
{
    cout << t.mynum << endl; //友元函数可以直接访问对象的内部私有和保护成员
}

void fun2(const Test& t)
{
    cout << t.mynum << endl; //这里会报错
}//其他函数只能访问公开成员

   

特性

  • 友元函数可访问类的私有和保护成员,但不是类的成员函数
  • 友元函数不能用const修饰
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用与普通函数的调用原理相同

友元函数特殊使用场景

 

自定义类型重载流提取和流插入运算符

问题:当我们尝试去重载operator<<,然后发现没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator<<重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理。

//日期类重载流插入提取运算符 - 友元函数法
class Date
{
	friend ostream& operator<<(ostream& _cout, const Date& d);
	friend istream& operator>>(istream& _cin, Date& d);
public:
	Date(int year = 1970, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}

istream& operator>>(istream& _cin, Date& d)
{
	_cin >> d._year;
	_cin >> d._month;
	_cin >> d._day;
	return _cin;
}

友元类

 

使用 friend 修饰类并声明在其他类中可以成为该类的友元类!

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

class Time
{
	friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	void SetTimeOfDate(int hour, int minute, int second)
	{
		// 直接访问时间类私有的成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

特性

  • 友元关系是单向的,不具有交换性(比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行)
  • 友元关系不能传递
  • 如果C是B的友元, B是A的友元,则不能说明C时A的友元
  • 友元关系不能继承
  • 友元类可以在类定义的任何地方声明,不受类访问限定符限制

内部类


概述

 

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。内部类就是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元

class A
{
public:
  class B //B称为A的内部类
  {
   private:
        int Bnum;
  }; 

private:
     int Anum;
};

这里B是A的内部类(友元类),A是B的外部类!B可以访问A类的所有成员,但是A不能访问B类的所有成员!


特性

  • 内部类可以定义在外部类的public、protected、private都是可以的
  • 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名
  • 外部类的字节大小与内部类没有任何关系,两者相互独立
  • 与友元类的区别:内部类能受到外类的访问限定符限制
  • 内部类可以访问外类中的成员,而外类无法访问内部类的成员

内部类是平时使用极少,一般用于隐藏类!


匿名对象


class Test
{
    Test(int n = 0)
    :num(n)
    {    
        cout<<"Test()"<<endl;
    }

    ~Test()
    {    
        cout<<"~Test()"<<endl;
    }
private
    int num;
};


int main()
{
    Test(); //构造匿名对象
    return 0;
}

通过 类名() 的方式可以构造匿名对象,匿名对象最大的特性就是生命周期只在一行,对于一些对象只用一次的情况,可以使用匿名对象优化性能。

    

匿名对象的生命周期在某些条件下会延长,例如被const引用变量引用后,其生命周期延长至函数执行结束!


编译器对于自定义类型的一些优化


编译器在实例化,传参和隐式类型转换时并非按正常的流程运行,而是进行了一些优化(简化了执行流程),从而提升执行效率!


//示例代码 - 接下来的测试以此类进行
class Test
{
public:
    Test(int n = 0) //构造函数
    :num(n)
    {
        cout<<"Test(int n = 0)"<<endl;
    }

    Test(const Test& t) //拷贝构造函数
    {
        num = t.num;
        cout<<"Test(const Test& t)"<<endl;
    }

    Test& operator=(const Test& t) //赋值重载函数
    {
        num = t.num;
        cout<<"operator="<<endl;
        return *this;
    }

private:
    int num;
};

隐式类型转换的优化 

  

int main()
{
    Test t = 10;
    return 0;
}

优化前:int类型转换为Test类型都临时对象,拷贝构造临时对象构造对象 t .

//相当于
//10 -> Test tmp(10) -> Test t(tmp)
// 先方式类型转换     再拷贝构造

优化后: 直接将10作为构造参数构造对象 t . 

//相当于
Test t(10);

传参优化

    

void fun(Test t) {}

int main()
{
    fun(10);
    return 0;
}

优化前:int类型转换为Test类型(与以上隐式类型转换相同),然后 t 通过拷贝构造形成对象.

//相当于
//10 -> Test tmp(10) -> Test t(tmp)
// 先方式类型转换     再拷贝构造

优化后:直接构造形成对象.

这里与隐式类型转换的优化相似!


返回值优化

    

Test fun()
{
    return Test(10); //匿名对象
}

int main()
{    
    Test t = fun();
    return 0;
}

优化前:构造匿名对象,返回匿名对象的拷贝临时变量,将临时对象拷贝构造形成对象 t.

优化后:通过返回的匿名对象,直接构造对象 t .


说明

  • 引用传参时,编译器无需优化,因为不会涉及拷贝构造
  • 实际编码时,如果能采用匿名构造,就用匿名构造,会加速编译器的优化
  • 接收参数时,如果分成两行(先定义、再接收),编译器无法优化,效率会降低

编译器会将不必要的 拷贝构造和构造 步骤去掉,优化为直接构造!

  

编译器只能在一行语句内进行优化,如果将这些语句拆分为多条语句,编译器则不会优化,因为编译器不能擅自主张。

//例如
int main()
{
    Test t;
    t = 10;
    return 0;
}


合理使用优化

  

因为编译器对这些情况有优化,所以我们使用以下技巧可以提高程序效率!

  • 接收返回值对象时,尽量拷贝构造方式接收,不要赋值接收
  • 函数返回时,尽量返回匿名对象
  • 函数参数尽量使用 const& 参数

关于对象的理解


现实生活中的实体计算机并不认识,计算机只认识二进制格式的数据。如果想要让计算机认识现实生活中的实体,用户必须通过某种面向对象的语言,对实体进行描述,然后通过编写程序,创建对象后计算机才可以认识。比如想要让计算机认识洗衣机,就需要:

  1. 用户先要对现实中洗衣机实体进行抽象---即在人为思想层面对洗衣机进行认识,洗衣机有什么属性,有那些功能,即对洗衣机进行抽象认知的一个过程。
  2. 经过1之后,在人的头脑中已经对洗衣机有了一个清醒的认识,只不过此时计算机还不清楚,想要让计算机识别人想象中的洗衣机,就需要人通过某种面相对象的语言(比如:C++、Java、Python等)将洗衣机用类来进行描述,并输入到计算机中
  3. 经过2之后,在计算机中就有了一个洗衣机类,但是洗衣机类只是站在计算机的角度对洗衣机对象进行描述的,通过洗衣机类,可以实例化出一个个具体的洗衣机对象,此时计算机才能洗衣机是什么东西。
  4. 用户就可以借助计算机中洗衣机对象,来模拟现实中的洗衣机实体了。

在类和对象阶段,大家一定要体会到,类是对某一类实体(对象)来进行描述的,描述该对象具有那些属性,那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象。


最后

<类和对象 - 下> 的知识介绍到这里就结束了!本篇介绍了类变量的定义在初始化列表以及初始化列表的合理使用,static成员的定义,友元概念和编译器的一些优化,相信这些细节的加持下,我们能加深对类和对象的理解,更加合理的使用类,类和对象的知识。类和对象的知识介绍到这里就结束了,希望小伙伴们都能理解!

本次 <C++类和对象 - 下> 就介绍到这里啦,希望能够尽可能帮助到大家。

如果文章中有瑕疵,还请各位大佬细心点评和留言,我将立即修补错误,谢谢!

🌟其他文章阅读推荐🌟

C++ <类和对象 - 中> -CSDN博客 

C++ <类和对象 - 上> -CSDN博客

C++入门知识 -CSDN博客

🌹欢迎读者多多浏览多多支持!🌹

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

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

相关文章

重构类关系-Push Down Method函数下移四

重构类关系-Push Down Method函数下移四 1.函数下移 1.1.使用场景 超类中的某个函数只与部分&#xff08;而非全部&#xff09;子类有关。将这个函数移到相关的那些子类去。 Push Down Method (328)与Pull Up Method (322)恰恰相反。当我有必要把某些行为从超类移至特定的子…

maven的profiles功能介绍、maven自定义插件plugin

profiles maven配置文件的profiles功能类似于springboot的spring.profiles.active配置&#xff0c;指定不同的环境来读取相应的配置内容。 <profiles>标签的子标签可以包含<repository> <plugin> <dependencies> <distributionManagement>等。 …

go语言gin框架学习

让框架去做http解包封包等&#xff0c;让我们的精力用在应用层开发 MVC模式 M: model&#xff0c;操作数据库gorm view 视图 处理模板页面 contoller 控制器 路由 逻辑函数 解决gin相关代码飘红的问题 记得启用gomodule go env -w GO111MODULEon然后到相应目录下执行 go mod i…

Karl Guttag:论相机对焦技术在AR/VR中的沿用

近期&#xff0c;AR/VR光学专家Karl Guttag介绍了两家在CES 2023展出光学传感技术的公司&#xff1a;poLight和CML&#xff08;剑桥机电一体化&#xff09;。​同时介绍两家公司的原因&#xff0c;是因为他们提供了实现AR/VR“光学微动”&#xff08;Optics Micromovement&…

5.多线程学习

作者&#xff1a;爱塔居 专栏&#xff1a;JavaEE 作者简介&#xff1a;大三学生&#xff0c;喜欢总结与分享~ 文章目录 目录 文章目录 章节回顾 一、wait 和notify 二、设计模式 2.1 单例模式 章节回顾 线程安全 1.一个线程不安全的案例&#xff08;两个线程各自自增5w次&…

PyTorch 之 基于经典网络架构训练图像分类模型

文章目录一、 模块简单介绍1. 数据预处理部分2. 网络模块设置3. 网络模型保存与测试二、数据读取与预处理操作1. 制作数据源2. 读取标签对应的实际名字3. 展示数据三、模型构建与实现1. 加载 models 中提供的模型&#xff0c;并且直接用训练的好权重当做初始化参数2. 参考 pyto…

【Linux】基于阻塞队列的生产者消费者模型

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《学会Linux》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;为何要使用…

谈谈低代码的安全问题,一文全给你解决喽

低代码是一种软件开发方法&#xff0c;通过使用图形化用户界面和可视化建模工具&#xff0c;以及自动生成代码的技术&#xff0c;使得开发人员可以更快速地构建和发布应用程序。 作为近些年软件开发市场热门之一&#xff0c;市面上也涌现了许多低代码产品&#xff0c;诸如简道云…

SpringCloud:统一网关Gateway

目录 1、网关介绍 2、搭建网关服务 3、路由断言工厂 4、路由过滤器 5、全局过滤器GlobalFilter 6、过滤器执行顺序 7、跨域问题处理 1、网关介绍 网关(Gateway)又称网间连接器、协议转换器。网关在网络层以上实现网络互连&#xff0c;是复杂的网络互 连设备&#xff0…

常见背包问题

一.前言若你想学习或正在学习动态规划&#xff0c;背包问题一定是你需要了解的一种题型&#xff0c;并且大多数人最初都是从背包问题入坑进而打开动态规划这一大门。背包问题分为多种&#xff0c;你可以先掌握最常见的主要是三类&#xff1a;01背包、完全背包、多重背包二.分析…

C语言--动态内存管理1

目录前言动态内存函数介绍mallocfreecallocrealloc常见的动态内存错误对NULL指针的解引用操作对动态开辟空间的越界访问对非动态开辟内存使用free释放使用free释放一块动态开辟内存的一部分对同一块动态内存多次释放动态开辟内存忘记释放&#xff08;内存泄漏&#xff09;对通讯…

TCP和UDP协议的区别?

是否面向连接&#xff1a; TCP 是面向连接的传输&#xff0c;UDP 是面向无连接的传输。 是否是可靠传输&#xff1a;TCP是可靠的传输服务&#xff0c;在传递数据之前&#xff0c;会有三次握手来建立连接&#xff1b;在数据传递时&#xff0c;有确认、窗口、重传、拥塞控制机制…

Linux编辑器-vim

一、vim简述1&#xff09;vi/vim2&#xff09;检查vim是否安装2)如何用vim打开文件3)vim的几种模式命令模式插入模式末行模式可视化模式二、vim的基本操作1)进入vim&#xff08;命令行模式&#xff09;2)[命令行模式]切换至[插入模式]3)[插入模式]切换至[命令行模式]4)[命令行模…

【C语言进阶】动态内存管理

真正的人生&#xff0c;只有在经过艰难卓绝的斗争之后才能实现。 ——塞涅卡 目录 一.为什么存在动态内存分配&#xff1f; 二.动态内存管理的函数 1.malloc函数 2.free函数 ​3.calloc函数 4.realloc函数 三.常见的动态内存错误 1.对N…

python编程:使用pyecharts绘制拟合曲线图

pyecharts库是python下实现的echarts图表绘制库&#xff0c;接下来&#xff0c;我们使用pyecharts来绘制一条曲线&#xff0c;来体验一下pyecharts的基本使用效果。 1、首先&#xff0c;我们要安装下pyecharts库&#xff0c;在pycharm终端输入安装命令&#xff1a; pip install…

pytorch实现深度神经网络与训练

目录 1. 随机梯度下降算法 2.优化器 3. 损失函数 3.1 均方误差损失 3.2 交叉熵损失 4.防止过拟合 4.1 过拟合的概念 4.2 防止过拟合的方法 5. 网络参数初始化 5.1 网络参数初始化方法 5.2 参数初始化方法应用实例 1.针对某一层的权重进行初始化 2.针对一个网络的权…

基于ESP32做低功耗墨水屏时钟

基于ESP32做低功耗墨水屏时钟电子墨水屏概述ESP32实验低功耗电子时钟功能描述接线开发实验结果电子墨水屏 概述 电子墨水是一种革新信息显示的新方法和技术。和传统纸差异是电子墨水在通电时改变颜色&#xff0c;并且可以显示变化的图象&#xff0c;像计算器或手机那样的显示。…

使用ArcGIS为科研论文制作正确、美观、详细的插图

科研论文中的插图&#xff0c;如果图中包含地理信息&#xff0c;那么首先需要在图中标明指北针、比例尺、图例&#xff0c;然后在此基础上再对作的图进一步的美化和修改。 来源&#xff1a;https://doi.org/10.1016/j.uclim.2022.101326 这种就是属于是最常见的研究区概况图&a…

(只需五步)注册谷歌账号详细步骤,解决“此电话号码无法验证”问题

目录 第一步&#xff1a;打开google浏览器 第二步&#xff1a;设置语言为英语&#xff08;美国&#xff09; 第三步&#xff1a;点击重新启动&#xff0c;重启浏览器 第四步&#xff1a;开始注册 第五步&#xff0c;成功登录google账号&#xff01; 如果出现这样的原因&…

java多线程之线程安全(重点,难点)

线程安全1. 线程不安全的原因:1.1 抢占式执行1.2 多个线程修改同一个变量1.3 修改操作不是原子的锁(synchronized)1.一个锁对应一个锁对象.2.多个锁对应一个锁对象.2.多个锁对应多个锁对象.4. 找出代码错误5. 锁的另一种用法1.4 内存可见性解决内存可见性引发的线程安全问题(vo…
最新文章