类和对象的初始化(构造函数与析构函数)

el/2024/4/13 14:48:02

有对象一定要有空间,有空间不一定有对象。

class Empty
{};
int main()
{Empty e;cout << sizeof(e) << endl;//1字节return 0;
}

虽然此对象没任何属性和方法,但是要创建一个对象,就必须在地址空间标识此对象,就必须占有一个字节。

类的数据成员不能在类定义时初始化。

class student
{int num = 20060102;char name[15] = "张三";float score = 85;
};//错误

如果一个类中所有的数据成员都是公用的public,则可以在定义对象时对数据成员进行初始化。

class studet
{public:int num;char name[15];float score;
}stu1 = { 20010130101,"张三",85};

一、构造函数

数据成员多为私有的,要对它们进行初始化,必须用一个公有函数来进行。C++提供了构造函数来处理类对象的初始化问题。同时这个函数应该在且仅在定义对象时自动执行一次。称为构造函数(constructor) 。

构造函数就是用来在创建对象时初始化对象,为对象数据成员赋初始值。

构造函数用途:1)创建对象,2)初始化对象中的属性,3)类型转换。

构造函数是类的一种特殊的成员函数(在特殊用途中构造函数的访问限定可以定义成私有或保护)
,不需要人为调用;而是在建立对象时自动被执行。

特征:
1.C++规定构造函数的名字必须与类名相同。

2.构造函数无函数返回类型说明。注意是没有而不是void,即什么也不写,也不可写void。实际上构造函数有返回值返回的就是构造函数所创建的对象。

3.在程序运行时,当新的对象被建立,该对象所属的类构造函数自动被调用,在该对象生存期中也只调用这一次(由系统调用)

4.构造函数可以重载。严格地讲,类中可以定义多个构造函数,它们由不同的参数表区分,系统在自动调用时按一般函数重载的规则选一个执行。
5.构造函数可以在类中定义,也可以在类中声明,在类外定义。
6.如果类说明中
没有给出构造函数,则C++编译器自动给出一个缺省的构造函数.
            类名(void){     }

只要我们定义了一个构造函数,系统就不会自动生成缺省的构造函数。只要构造函数是无参或只要各参数均有缺省值的,C++编译器都认为是缺省的构造函数,并且缺省的构造函数只能有一个。默认构造函数——除了this指针以外没有参数的构造函数,函数体是空的,只能为对象开辟数据成员存储空间,而不能给对象中的数据成员赋初值。
构造函数定义形式:

类名(形式参数列表)

    {  函数体  }

1.1不带参数的构造函数

构造函数可以没有形参。

1>在类内定义构造函数:

#include<iostream>
using namespace std;
class student
{
public:student(){num = 20060102;strcpy(name,"张三");score = 85;}void display(){cout << "num:" << num << endl;cout << "name:" << name << endl;cout << "score:" << score << endl;}
private:int num;char name[15];float score;
};

2>在类外定义构造函数:

#include<iostream>
using namespace std;
class student
{
public:student();//类外定义构造函数void display(){cout << "num:" << num << endl;cout << "name:" << name << endl;cout << "score:" << score << endl;}
private:int num;char name[15];float score;
};
student::student()
{num = 20060102;strcpy_s(name, "张三");score = 85;
}

只要创建类的新对象,都要执行构造函数。定义对象自动调用构造函数

构造函数的主要用途就是:初始化类的数据成员。

用构造函数对对象进行初始化形式:

无参数的构造函数,定义对象的形式:

    类名  对象名1对象名2...... ;

有参数的构造函数,定义对象的形式:

       类名  对象名1(实参列表),对象名2(实参列表)...... ;

构造函数是一种成员函数,它具有一般成员函数的特点。

构造函数的名称与其类名相同

一个类中可定义一个或多个构造函数。

构造函数说明:

1) 构造函数是在创建对象时自动执行的,而且只执行一次,并先于其他成员函数执行。构造函数不需要人为调用,也不能被人为调用。

2) 构造函数一般声明为公有的(public),因为创建对象通常是在类的外部进行的。

3) 在构造函数的函数体中不仅可以对数据成员初始化,而且可以包含其他任意功能的语句,但是一般不提倡在构造函数中加入与初始化无关的内容。

带参数的构造函数对数据成员初始化,每一个对象的数据成员都得到同一组初值。

1.2带参数的构造函数

用带参数的构造函数对不同对象初始化

1>在类内定义构造函数

class  student {  public:student(int  n, string m, float s){  num = n;name = m;score = s;} void display( )                             {  cout<<”num: ”<< num<<endl;       cout<<”name: ”<< name<<endl;   cout<<”score: ”<< score <<endl;}private:int  num; string  name;    // 或char *name;float  score;} ;

2>在类外定义构造函数

class  student {  public:student(int  n, string m, float s);void display( )                             {  cout<<”num: ”<< num<<endl;      cout<<”name: ”<< name<<endl; cout<<”score: ”<< score <<endl;}private:int  num;string  name;   // 或 char *name;float  score;} ; student::student(int  n, string m, float s ){  num = n;name = m;  score = s;} 

1.3在构造函数中用参数初始化表对数据成员初始化

构造函数可以使用参数初始化表对数据成员进行初始化,不在函数体内,而在函数首部实现。

一般形式为:

类名(形式参数列表):构造函数初始化

         {   函数体

          }

与其他的成员函数一样,构造函数可以定义在类的内部或外部,但初始化表的构造函数只在类体中定义中

class  student {  public:student(int  n, string m, float s):num(n),name(m),score(s){ }               void display( )                             {  cout<<”num: ”<< num<<endl;       cout<<”name: ”<< name<<endl;   cout<<”score: ”<< score <<endl;}private:int  num; string  name;    // 或char *name;float  score;} ;

注意:如果数据成员是数组,就不能在参数初始化表对其进行初始化,应在函数体中用语句对其赋值。

初始化是在构建对象的时候赋值,赋值是在构建完成后才会赋值。

class  student {  public:student(int  n, float s, char m[ ] ):num(n), score(s)//此处是初始化              {  strcpy(name, m);//函数体中的叫赋值  } void display( )                             {  cout<<”num: ”<< num<<endl;       cout<<”name: ”<< name<<endl;   cout<<”score: ”<< score <<endl;}private:int  num; char  name[15];    float  score;} ;

初始化列表的顺序,并不是对象中成员初始化表的顺序,是按照类中属性声明的顺序构建

class  Complex
{
private:int Real;int Image;
public:Complex(int x) :Real{Image},Image{x}{}void Print(){cout << Real << " " << Image << endl;}
};
int main()
{Complex c1(10);c1.Print();return 0;
}

1.4构造函数重载

与一般的成员函数一样,在一个类中可以定义多个构造函数,即构造函数重载,只要每个构造函数的形参列表是唯一的。一个类的构造函数数量是没有限制的。

#include <iostream> 
using namespace std;
class  student {  public:student( );student(int  n, string m, float s):num(n),name(m),score(s){ }                void display( ) ;                            private:int  num; string  name;    float  score;} ; 
student::student( ){  num = 20060102;name = ”张三”;  score = 85;} 
void student::display( )                            {  cout<<”num: ”<< num<<endl;       cout<<”name: ”<< name<<endl;   cout<<”score: ”<< score <<endl;}
int main( ){ student stu1;stu1.display( );student stu2( 20060103,“李四”, 88);stu2.display( );return 0;}
构造函数使用说明:

  1)在建立对象时不必给出实参的构造函数,称为默认构造函数。无参构造函数属于默认构造函数。一个类只能有一个默认构造函数。

  2)如果未定义构造函数,系统会自动提供一个默认构造函数,但它的函数体是空的,不起初始化作用。

  3)尽管在一个类中可以包含多个构造函数,但是对于一个对象来说, 建立对象时只执行其中一个,并非每个构造函数都被执行。 

1.5带默认参数的构造函数

在实际应用中,有些构造函数的参数值通常是不变的,只是在一些特殊情况下才需改变其值——可定义默认参数的构造函数

#include<iostream>
#include<string>
using namespace std;class  student
{
public:student(int  n = 20060101, string m = "张三", float s = 85);void display();
private:int  num;string  name;float  score;
};
student::student(int  n, string m, float s)
{num = n;name = m;score = s;
}
void student::display()
{cout << "num: " << num << endl;cout << "name:" << name << endl;cout << "score:" << score << endl;
}
int main()
{student stu1;stu1.display();student stu2(20060103,"李四");stu2.display();return 0;
}
构造函数默认参数的说明:

   1必须在类的内部指定构造函数的默认参数,不能在类外部指定默认参数。

class  student {  public:student( int  n , string m , float s  );                void display( ) ;                            private:int  num; string  name;    float  score;} ; 
student::student( int n=20060101, string m=”张三”, float s=85  ){  num = n;name = m;  score = s; }    //错误

2如果构造函数的全部参数都指定了默认值,则在定义对象时可以给一个或几个实参,也可以不给出实参。这时,就与无参数的构造函数有歧义了。

3在一个类中定义了带默认参数的构造函数后,不能再定义与之有冲突的重载构造函数。

一般地,不应同时使用构造函数的重载和带默认参数的构造函数

1.6用构造函数实现初始化方法总结

  1)在类中定义的构造函数的函数体中对数据进行赋初值。

  2)用带参数的构造函数,可以使同类的不同对象中的数据具有不同的初值。

  3)在构造函数中用参数初始化表对数据赋初值。

  4)在定义构造函数时可以使用默认参数。

  5构造函数可以重载,即在一个类中定义多个同名的构造函数。

        一般不应同时使用有默认参数的构造函数和构造函数的重载。

#include<iostream>
using namespace std;class CGoods
{
private:enum{LEN=20};//枚举类型大小不计入结构体大小中
private:char Name[LEN];//直接将LEN替换为20int Amount;float Price;float Total;
public:CGoods(const char*name,int amount=0,float price=0.0):Amount(amount),Price(price),Total(amount*price){strcpy_s(Name, name);}CGoods(){}void GoodsInfo()const{cout << " Name " << Name << " Amount: " << Amount << " Price " << Price << " Total_value: " << Total << endl;}
};
int main()
{CGoods ca;CGoods car("bya", 10, 12.00);CGoods book("c++", 10, 128.00);car.GoodsInfo();book.GoodsInfo();return 0;
}

1.7 对象的定义

下列定义的对象哪一个是错误的:

class  Complex
{
public:Complex(){}Complex(int r,int i):Real(r),Image(i){}void Print(){cout << Real << " " << Image << endl;}private:int Real;int Image;
};
int main()
{Complex c1;Complex c2();Complex c3(12, 23);Complex c4 = Complex(1, 2);//直接将创建的对象给c4,没有借助中间对象,相当于Complex c4(1,2);c4 = Complex(3,4);//此种情况是创建一个无名对象,将无名对象的值给c4,因为c4此时存在,如果直接将构建的值给c4,那么c4就被构建了两次,这是不允许的Complex c5{};Complex c6{ 1,2 };c1.Print();return 0;
}

定义的c2不是对象

 c2系统认为是函数的声明,系统将 类型 名称 ()认为是函数的声明

如何想用()初始化对象,要么就要带默认值(eg:c3),不带默认值就不要写括号(c1)

1.8构造函数的用途——类型转换

class Int
{
private:int value;
public:Int(int x=0):value(x){}void Print()const { cout << value << endl; }
};
int main()
{Int a(10);//自己设计的类型a.Print();//10int x = 100;//系统内置类型a = x;//将int类型的x赋值给Int类型的对象a//隐式转换,用x的值构建一个临时对象,将这个临时对象赋值给a,赋值完成后,临时对象销毁,此临时对象称为将亡值a = (Int)x;//显式转换a.Print();//100return 0;
}

把一个变量给对象时,看起来像是把变量给对象赋值,实则不然。

把一个变量给对象,先调动对象的构造函数构建一个临时量,再把这个临时量赋值给对象。赋值完成后,临时量就会销毁,此临时量也称作将亡值。

关键字explicit

不允许隐式转化

构造函数要想可以类型转换,必须只有一个参数或者参数有默认值。

class Int
{
private:int value;
public:Int(int x,int y):value(x+y){}void Print()const { cout << value << endl; }
};
int main()
{Int a(10,20);a.Print();int x = 1, y = 2;a = x, y;//errora = (Int)(x, y);//强转也不行return 0;
}

构造函数有了默认值就可以了,就剩一个没默认值的参数

class Int
{
private:int value;
public:Int(int x,int y=0):value(x+y){}void Print()const { cout << value << endl; }
};
int main()
{Int a(10,20);a.Print();//30int x = 1, y = 2;a = x, y;//强转,只能是单参a = (Int)(x, y);//构造函数此时单参,只需要传一个参数,逗号表达式的值为最右边的值a.Print();//2//构建一个对象给aa = Int(x, y);a.Print();//3return 0;
}

二、析构函数

当对象脱离其作用域时(如对象所在的函数已调用完毕),系统会自动执行析构函数

析构函数也是一种特殊的成员函数,执行的是与构造函数相反的操作——通常用作执行一些清理任务(如释放对象所占用得内存等)。

析构函数定义形式:

~类名( )
    {

        函数体

    }

2.1析构函数的特点:

1.析构函数与构造函数的名字相同,但在其前面加上“~”。

2.析构函数没有任何参数,没有返回值也没有返回类型不能重载

3.一个类中只能有一个析构函数

4.对象销毁时,系统自动调用析构函数。

5.如果类说明中没有给出析构函数,则C++编译器自动给出一个缺省的析构函数。

eg:    ~类型名称(){}

class student 
{
public:student(int n, string m, float s){num = n;name = m;score = s;}~student(){}void display(){cout << "num:" << num << endl;cout << "name:" << name << endl;cout << "score:" << score << endl;}
private:int num;string name;float score;
};
int main()
{student stu1(2020, "zhangsan", 80);stu1.display();student stu2(2021, "lisi", 90);stu2.display();return 0;
}

2.2析构函数说明:

1.每个类必须有一个析构函数,如果未定义析构函数,则系统自定义一个析构函数;
2.对于大多数类而言,不需要显式地编写析构函数,尤其是具有构造函数的类不一定需要定义自己的析构函数。析构函数通常用于释放在构造函数或在对象生命期内获取的资源(如动态分配的内存)。

2.3何时调用析构函数:

1)对象在程序运行超出其作用域时自动撤销,撤销时自动调用该对象的析构函数。如函数中的非静态局部对象。
2)如果用new运算动态地建立了一个对象,当用delete运算释放该对象时,调用该对象的析构函数。

2.4调用构造函数和析构函数的顺序

在使用构造函数和析构函数时,需要特别注意对它们的调用时间和调用次序。
构造函数和析构函数的调用很像一个栈的先进后出,调用析构函数的次序正好与调用构造函数的次序相反。最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用。

先构造的后析构,后构造的先析构。

三、对象的生存期

3.1 局部对象

1>对于局部定义的对象,每当程序控制流到达该对象定义处时,调用构造函数。当程序控制走出该局部域时,则调用析构函数。


class  Complex
{
private:int Real;int Image;
public:Complex(int r=0,int i=0) :Real(r), Image(i){cout << "Create Complex " << endl;}~Complex() {cout << "Destory Complex" << endl;}void Ptint()const{cout << "Real: " << Real << "Image: " << Image << endl;}void Print(){cout << Real << " " << Image << endl;}
};
void fun()
{Complex c1(1, 2);cout << "input block" << endl;{Complex c2(4, 5);}cout << "out block" << endl;return;
}

2>对于静态局部定义的对象,在程序控制首次到达该对象定义处时,调用构造函数。当整个程序结束时调用析构函数。

单纯的局部对象,tmp对象一直在构建析构构建析构,总共10次

class  Complex
{
private:int Real;int Image;
public:Complex(int r=0,int i=0) :Real(r), Image(i){cout << "Create Complex " << endl;}~Complex() {cout << "Destory Complex" << endl;}void Ptint()const{cout << "Real: " << Real << "Image: " << Image << endl;}void Print(){cout << Real << " " << Image << endl;}
};
void fun()
{Complex tmp(1, 2);
}int main()
{for (int i = 0; i < 10; ++i){fun();}return 0;
}

当fun中的对象为静态局部对象时

void fun()
{static Complex tmp(1, 2);//静态对象,在数据区,有一个标记,运行时下次再遇到查看标记发小存在就不再构建
}int main()
{for (int i = 0; i < 10; ++i){fun();}return 0;
}

 

 在函数第一次调用时只构建一次,也析构一次。

3.2 全局对象

对全局定义的对象,当程序进入入口函数main之前对象就已经定义,这时要调用构造函数。整个程序结束时调用析构函数。

例:下列对象a,b,c,哪一个先构建

class Int
{
private:int value;
public:Int(int x = 0) :value(x) {cout << "Create Int: " << value<<endl;}~Int(){cout << "Destory Int: " << value << endl;}
};
Int a(1);
int main()
{Int b(2);return 0;
}
Int c(3);

 

我们可以看到,a,c,b按照此顺序构建

 a和c是全局对象,b是主函数中的对象,在进入主函数之前,全局变量和全局对象就要构建出来,进入主函数之后,主函数中的对象才会被构建

3.3 动态创建的对象

动态创建的对象,使用new创建对象,delete释放对象.

malloc申请空间

1>下列程序可以运行成功吗?show可以被cp指针调用吗

class  Complex
{
private:int Real;int Image;
public:Complex(int r = 0, int i = 0) :Real(r), Image(i){cout << "Create Complex " << endl;}~Complex() {cout << "Destory Complex" << endl;}void Print()const{cout << "Real: " << Real << "Image: " << Image << endl;}void show(){cout << "Complex::show " << endl;}
};
int main()
{Complex* cp = nullptr;cp->show();return 0;
}

//show(cp),传的是个空指针,this指针为nullptr,但是show方法中没有用到this指针所以可以调用show

class  Complex
{
private:int Real;int Image;
public:Complex(int r = 0, int i = 0) :Real(r), Image(i){cout << "Create Complex " << endl;}~Complex() {cout << "Destory Complex" << endl;}//void Print( Complex *const this)constvoid Print()const{//cout << "Real: " << this->Real << "Image: " << this->Image << endl;cout << "Real: " << Real << "Image: " << Image << endl;}void show(){cout << "Complex::show " << endl;}
};
int main()
{Complex* cp = nullptr;cp->Print();//Print(cp),this指针为nullptrreturn 0;
}

2>那么cp可以调用print方法吗?

cp->print();

我们可以看到并不能调用成功。

Print方法中含有this指针,此时cp为nullptr,this指针为空,0地址不允许操作,

3>使用malloc申请空间的对象调用print方法可以成功吗

int main()
{Complex* cp = (Complex*)malloc(sizeof(Complex));if (cp == nullptr)return 1;cp->Print();return 0;
}

我们可以看到可以调用print方法,但是值为随机值。

 有空间但是没对象,

malloc给cp申请了4字节(X86)的空间,Print(cp);cp指向申请的空间,this指针也指向这个堆区空间,但是没有对象,所以值为随机值。

给cp构建一个对象:

int main()
{Complex c1(1, 2);Complex* cp = (Complex*)malloc(sizeof(Complex));if (cp == nullptr)return 1;cp->Print();*cp = c1;cp->Print();return 0;
}

 此时cp的值就不为随机值了

不允许拿对象给空间赋值,可以在空间中构建对象,但不允许用对象给空间赋值

如果拿对象给空间赋值,容易出问题,eg:virtual虚函数

virtual void Print()const{cout << "Real: " << Real << "Image: " << Image << endl;}

new申请空间 

class  Complex
{
private:int Real;int Image;
public:Complex(int r = 0, int i = 0) :Real(r), Image(i){cout << "Create Complex " << endl;}~Complex() {cout << "Destory Complex" << endl;}virtual void Print()const{cout << "Real: " << Real << "Image: " << Image << endl;}void show(){cout << "Complex::show " << endl;}
};int main()
{Complex* cp = new Complex(1, 2);//new 1.自动计算类型大小 2.调用malloc从堆区申请空间 3.调动构建函数构建对象 4.把对象地址给cpcp->Print();delete cp;//1.调用析构函数析构cp 2.将cp的堆空间还给系统,cp此时变成失效指针,main函数结束时,cp栈区空间才会释放cp = nullptr;return 0;
}

四、new三种用法:

4.1作为运算法(关键字)

p = new Pointer(10,20);//p是指针,Pointer 是类对象

new调用malloc在heap申请空间,调用Pointer类型在空间构建对象,返回。

4.2定位(构建)new

只要给定位new一个地址,就可以在此地址创建对象。

new(s) Pointer(12,23);//在s指向的空间调动构造函数,构建对象

delete p;//delete调动析构函数释放空间,再调用free销毁。

#include<iostream>
using namespace std;
class Object
{
private:int val;
public:Object(int x){val = x;cout << "Create:" << val << endl;}};Object ObjA(1);
int main()
{Object Objb(2);
}
Object objc(3);

进入主函数之前就要为全局对象构建,再进入主函数编译从上到下,

int main()
{int a = 10;int b(10);//相当于调动了一个带参数的构造函数,拿10构建b/*伪构造函数*/int c = int(10);//类型名+(),调动其构造函数,构建c对象int* p = new int(20);//new申请int空间,调动int的构造函数构造整型
}

一个对象只能被构建一次,但可借助new实现二次构建,借助定位new可以对任何对象重新构建。

class  Complex
{
public:Complex(){}Complex(int r,int i):Real(r),Image(i){}void Print(){cout << Real << " " << Image << endl;}private:int Real;int Image;
};
int main()
{Complex c1;Complex c2(12, 23);c1(23,34);//erro,对象只能构建一次new(&c1)Complex(23, 34);c1.Print();c2.Print();return 0;
}

4.3作为一个函数

 
int main()
{/* operator new,函数方法的使用,和malloc一样,需要sizeof计算空间大小,需要对返回值强转*/int* ip = new int(10);int* is = (int*)::operator new(sizeof(int));/*operator new 和 malloc 的不同*///malloc申请空间空间不足会返回空指针//operator new 申请空间失败会抛出异常throw bad_alloc,所以使用new或operator new 判断是否申请成功不能用判空判断//可以使用nothrow,让new或者operator new申请空间失败不抛出异常,返回一个空指针int* ip = new(std::nothrow) int(10);int* is = (int*)::operator new(sizeof(int),std::nothrow);delete ip;//new 申请的空间用delete释放::operator delete(is);//operator new 申请的空间需要用::operator delete释放return 0;
}


http://www.ngui.cc/el/4994641.html

相关文章

共用数据的保护

C有不少措施保护数据的安全性,如private保护类的数据成员等。但对于一些共用的数据&#xff0c;如函数实参与形参等,我们可以在不同的场合通过不同的途径访问同一个数据对象。有时不经意的误操作会改变数据的值。 一、常对象 既要使数据能在函数间共享&#xff0c;又要保证它…

对象的动态建立和释放,赋值和复制

一、对象的动态建立和释放 利用new运算符可以动态地分配对象空间&#xff0c;delete运算符释放对象空间。 动态分配对象的一般形式: new 类名; 用new运算符动态分配得到的对象是无名的,它返回一个指向新对象的指针的值&#xff0c;即所分配的内存单元的起始地址。程序通过这…

文件查看命令和用户管理命令

文件查看命令 cat 1.用于查看文件数据 cat a.txt 2.合并文件 cat a.txt b.txt > c.txt 3.向文件中写入数据 cat > d.txt 这样写入数据有一点需要注意&#xff1a;cat > d.txt 输入数据时&#xff0c;会先将d.txt中的数据清空。 cat >> d.txt 向文件的末…

左值右值,柔性数组

一、右值、左值 在c中&#xff0c;左值就是可以被赋值的&#xff0c;右值就是不可被赋值的 在c11标准下: 所有的值必属于左值、右值两者之一。 右值分为纯右值和将亡值 在C11中可以取地址的、有名字的就是左值&#xff0c;反之&#xff0c;不能取地址的、没有名字的就是右值&a…

C++统一初始化和输入输出

一、c统一初始化 C语言中初始化一个量只有赋值语句这一种办法 c中初始化方式比较多 #include<iostream> using namespace std;int main() {int a 10;//c语言中初始化只有赋值语句这一种方法//以下都是c初始化的方法int b(10);//这样有点像对象初始化的形式int c{ 10 …

如何判断是以c++方式编译还是c方式编译

如何判断是以c方式编译还是c方式编译&#xff1f; 通过宏判断&#xff0c;c方式编译有宏 _cplusplus c中没有_cplusplus 在程序中可以利用开关语句&#xff08;探测宏&#xff09; #ifdef _cplusplusprintf("c"); #elseprintf("c"); #endif

关于log4j和slf4j的使用说明

1.log4j是日志类基础&#xff0c;slf4j需要依赖他&#xff0c;同时还需要一个log4j和slf4j的媒介来整合他们俩。简而言之&#xff0c;log4jslf4j&#xff08;slf4j--log4j&#xff09;三位一体才能爽歪歪&#xff01; 2.三者的版本如何搭配选择&#xff1f;答案是&#xff0c;…

通过jug 2.0.jar的成功下载的猜想

1.maven的配置为以下方式时&#xff0c;下载出错 <dependency> <groupId>org.safehaus.jug</groupId> <artifactId>jug</artifactId> <version>2.0.0</version> </dependency> 2.maven以以下配置时&#…

关于项目突然启动缓慢或者停留在Initializing Spring FrameworkServlet xx的原因

1.原因很简单&#xff0c;因为你的项目里出现了断点&#xff0c;所以加载项目很慢&#xff0c;如果你给tomcat设置启动时间了&#xff0c;那么通tomcat就启动失败&#xff01; 2.至于你为什么仔细检查了项目&#xff0c;都没有发现断点&#xff0c;原因很简单&#xff0c;你是…

hql出现could not initialize proxy - no Session的另外一个原因

1.重中之重的原因是表中有非空字段&#xff0c;但是你save or update之时没有注意这个&#xff0c;然后才报了这个错误&#xff01; 2.当然还有就是延迟加载设置的策略&#xff0c;这个你可以搜索别的文章看一看如何设置