目录
C++类与对象的收尾工作,构造函数的初始化列表,static成员函数,explicit关键字 ,拷贝对象的编译器优化问题;
class Date
{
private:
int _year;
int _month;
int _day;
public:
//构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;//函数体内赋初值
_day = day;
}
};
int main()
{
Date d(2000,12,20);
return 0;
}
构造函数的函数体内赋初值不能称为对对象中成员变量的初始化, 构造函数体中的语句只能将其称为赋初值 ,而不能称作初始化,因为 初始化只能初始化一次,而构造函数体内可以多次赋值;
初始化列表 :以一个 冒号开始 ,以 逗号分隔的数据成员列表 ,每个" 成员变量 "后面跟 一个 放在括号中的初始值或表达式 ;
class Date
{
private:
int _year;
int _month;
int _day;
public:
//构造函数
Date(int year, int month, int day)
//初始化列表为每个成员变量定义的地方;
: _year(year)
, _month(month)
, _day(day)
{
}
};
int main()
{
//类型创建对象的位置是对象整体定义的位置,对象中每个成员变量在何处定义?
Date d(2000,12,20);
return 0;
}
每个成员变量在初始化列表中 只能出现一次 ,因为对于成员变量不能定义2次, 初始化列表是成员变量定义的位置;
类中包含如下成员,必须在初始化列表位置进行初始化:
- 引用成员变量(引用变量的引用特性为必须在定义处初始化,因为引用必须引用一个实体,所以不可在构造函数函数体内赋初值来初始化);
- const成员变量(const修饰的成员变量,具有只读的特性,不允许被修改,只有一次修改的机会即在定义处,所以const成员变量必须在定义处初始化);
class Date
{
private:
int _year;
int _month;
int _day;
const int _n;
int& _ref;
public:
//构造函数
Date(int year, int month, int day)
//初始化列表为每个成员变量定义的地方;
: _n(10) //const成员变量
, _ref(year) //引用变量
{
_year = year;
_month = month;
_day = day;
}
};
int main()
{
Date d(2000,12,20);
return 0;
}
用户在初始化列表位置处显示初始化,按照用户初始化的方式进行初始化;用户在初始化列表位置处未显示初始化, 内置类型默认初始化为随机值,自定义类型成员调用其默认构造函数进行初始化;
class A
{
private:
int _a;
public:
A(int a = 0)
:_a(a)
{
}
};
class Date
{
private:
int _year;
int _month;
int _day;
const int _n;
int& _ref;
A _aa;//自定义类型
public:
//构造函数
Date(int year, int month, int day)
//初始化列表为每个成员变量定义的地方;
: _n(10) //const成员变量
, _ref(year) //引用变量
{
}
};
int main()
{
Date d(2000,12,20);
return 0;
}
监视窗口:
3. 自定义类型成员(且该类没有默认构造函数)必须在初始化列表位置初始化;
class A
{
private:
int _a;
public:
//构造函数,非默认构造函数
A(int a )
:_a(a)
{
}
};
class Date
{
private:
int _year;
int _month;
int _day;
const int _n;
int& _ref;
A _aa;//自定义类型
public:
//构造函数
Date(int year, int month, int day)
//初始化列表为每个成员变量定义的地方;
: _n(10) //const成员变量
, _ref(year) //引用变量
, _aa(10) //自定义类型成员
{
}
};
尽量使用初始化列表初始化,但是若禁用函数体内赋初值的方式,只使用初始化列表,可以吗?
class Stack
{
private:
int* _a;
int _capacity;
int _top;
public:
Stack(int capacity = 4)
:_a((int*)malloc(sizeof(int)*capacity))
, _capacity(4)
, _top(4)
{
if (_a == nullptr)
{
perror("malloc failed");
exit(-1);
}
}
};
不能禁用函数体内初始化,因为某些初始化或者检查的工作初始化列表也不能全部解决,如上所述对于栈类的数组进行动态内存开辟所开辟的空间是否开辟成功需要检查;
成员变量 在 类中声明次序 就是其在 初始化列表中的初始化顺序 , 与其在初始化列表中的先后次序无关;如下程序输出的结果是多少?
class A
{
public:
A(int a)
:_a1(a)
, _a2(_a1)
{
}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2;
int _a1;
};
int main()
{
A aa(1);
aa.Print();
}
运行结果:
先用_a1初始化_a2,_a1为随机值,所以_a2为随机值,再用a初始化_a1且a=1,所以_a1=1
static修饰局部变量时,会改变局部变量的存储位置,从而使得局部变量的生命周期变长;
void Func()
{
int a = 0;
static int b = 0;
a++;
b++;
cout << "a=" << a << "b=" << b << endl;
}
int main()
{
for (int i = 0; i < 2; i++)
{
Func();
}
return 0;
}
运行结果:
普通的局部变量(a)创建后存放于栈区,这种局部变量进入作用域创建,出作用域销毁;
static修饰的局部变量(b)创建后存放于静态区,改变了局部变量的存储位置,从而使得局部变量的生命周期延长,延长至程序结束时才销毁,但是static修饰的局部变量只改变生命周期,不改变作用域;
static修饰全局变量时,会改变全局变量的链接属性,从而使得全局变量的作用域变小;
全局变量只要在一个源文件中定义,整个工程中的其他源文件、对象、函数皆可以使用,生命周期贯穿整个工程;文件中的全局变量想要被另一个文件使用时需要使用extern关键字进行外部声明;
//data.cpp文件
int k=10;
//test.cpp文件
extern int k;//外部声明
int main()
{
cout << "k=" << k << endl;
return 0;
}
运行结果:
当使用static修饰全局变量k时,发生链接错误,全局变量k无法被test.cpp文件调用,static修饰全局变量使得全局变量的作用域变小,全局变量无法被其他文件使用;
//data.cpp文件
static int k=10;
//test.cpp文件
extern int k;//外部声明
int main()
{
cout << "k=" << k << endl;
return 0;
}
运行结果:
全局变量本身具有外部链接属性,在A文件定义的全局变量,在B文件中可以通过"链接"使用;但是全局变量被static修饰,全局变量的外部链接属性被修改为内部链接属性,此时全局变量只能在定义时的源文件中使用;
声明为 static的类成员称为 类的静态成员,用 static修饰的 成员变量,称之为 静态成员变量;用 static修饰的 成员函数,称之为 静态成员函数;
class A
{
private:
public:
//构造函数
A()
{}
//拷贝构造函数
A(const A& t)
{}
//析构函数
~A()
{}
};
A func()
{
A aa;
return aa;
}
int main()
{
A aa;
func();
return 0;
}
什么方法可以识别A类创建了多少对象?
定义全局变量count,当用类创建对象时,一定调用构造函数,构造函数函数体内++count;用已经存在的对象创建新对象时调用拷贝构造函数,拷贝构造函数的函数体内++count;此时便可以统计出创建了多少对象,为防止命名冲突,使用命名空间;
namespace zbw
{
int count = 0;
}
class A
{
private:
public:
//构造函数
A()
{
zbw::count++;
}
//拷贝构造函数
A(const A& t)
{
zbw::count++;
}
//析构函数
~A()
{}
};
A func()
{
A aa;
return aa;
}
int main()
{
A aa;
func();
cout << "count=" << zbw::count << endl;
return 0;
}
运行结果:
但是全局变量可以被任意修改,而普通成员变量是类中某个对象所独有的,在类的作用域发挥作用,不会被任意修改,相比于全局变量更加安全,将全局变量count修改为A类的成员变量,但是每个对象均存在count,根本无法完成计数;
class A
{
private:
int count = 0;//成员变量
public:
//构造函数
A()
{
count++;
}
//拷贝构造函数
A(const A& t)
{
count++;
}
//析构函数
~A()
{}
};
A func()
{
A aa;
return aa;
}
int main()
{
A aa;
func();
return 0;
}
C++为了支持全局变量成为某个类的专属,在成员变量前加上static,此时这个成员变量属于所有对象,但是不再支持成员变量处给缺省值,因为缺省值是给初始化列表,初始化列表只是初始化某一个对象,static所修饰的成员变量并不是只属于一个对象;静态成员变量必须在类外定义,定义时不添加static关键字,类中声明;静态成员变量还受访问限定符的限制;
class A
{
private:
//静态成员变量声明
static int count;
public:
//构造函数
A()
{
count++;
}
//拷贝构造函数
A(const A& t)
{
count++;
}
//析构函数
~A()
{}
};
//静态成员变量定义
int A::count = 0;
A func()
{
A aa;
return aa;
}
int main()
{
A aa;
func();
//count++; //静态成员变量受访问限定符的限制
return 0;
}
若访问限定符为 public ,静态成员变量可用 类名::静态成员 或者 对象.静态成员 访问;
class A
{
//private:
public:
//静态成员变量声明
static int count;
//构造函数
A()
{
count++;
}
//拷贝构造函数
A(const A& t)
{
count++;
}
//析构函数
~A()
{}
};
//静态成员变量定义
int A::count = 0;
int main()
{
A aa;
cout << A::count << endl; //类名::静态成员
cout << aa.count << endl; //对象.静态成员
return 0;
}
若访问限定符为private,无法以上述访问方式访问,需要提供一个函数接口实现访问;
class A
{
private:
//静态成员变量声明
static int count;
public:
//构造函数
A()
{
count++;
}
//拷贝构造函数
A(const A& t)
{
count++;
}
//析构函数
~A()
{}
};
//静态成员变量定义
int A::count = 0;
int main()
{
A aa;
cout << A::count << endl;
cout << aa.count << endl;
return 0;
}
class A
{
private:
//静态成员变量声明
static int count;
public:
//函数接口
int GetCount()//只读不写
{
return count;
}
//构造函数
A()
{
count++;
}
//拷贝构造函数
A(const A& t)
{
count++;
}
//析构函数
~A()
{}
};
//静态成员变量定义
int A::count = 0;
int main()
{
A aa;
cout << aa.GetCount()<< endl;
return 0;
}
普通的成员函数必须使用对象调用,如aa.GetCount()函数,此种调用方式具有两种意义,其一是成员函数GetCount()属于对象aa的类,其二是成员函数具有一个隐藏的参数(this指针),传递对象aa的地址;而静态成员函数没有隐藏的this指针,效率更高;
class A
{
private:
//静态成员变量声明
static int count;
public:
//静态成员函数,特点:没有this指针
static int GetCount()
{
return count;
}
//构造函数
A()
{
count++;
}
//拷贝构造函数
A(const A& t)
{
count++;
}
//析构函数
~A()
{}
};
//静态成员变量定义
int A::count = 0;
int main()
{
A aa;
cout <<A::GetCount()<< endl;
return 0;
}
静态成员函数可以访问非静态的成员变量吗?
不可以,因为静态成员函数没有this指针,无论成员变量还是成员函数皆通过this指针进行访问;
总结:
静态成员函数与静态成员变量本质为受限制的全局变量与全局函数;
构造函数不仅可以构造与初始化对象, 单个参数的构造函数 或者 第一个参数无默认值其余均有默认值的构造函数 或者 全缺省参数的构造函数 ,还具有 类型转换 的作用;
class A
{
private:
int _a = 0;
public:
//单参数的构造函数,参数类型为int,支持int转换为A类
A(int a)
:_a(a)
{
}
};
int main()
{
//A为自定义类型,10为整型-->内置类型对象隐式类型转换为自定义类型对象
A aa = 10;
return 0;
}
A aa=10; 隐式类型转换过程中产生临时变量,临时变量的类型为A,临时变量具有常属性;
?explicit修饰构造函数,禁止隐式类型转换;显式转换(强制类型转换)explicit不起作用;
class A
{
private:
int _a = 0;
public:
//构造函数
explicit A(int a)
:_a(a)
{
}
};
int main()
{
A aa = 3;
return 0;
}
?运行结果:
class Date
{
private:
int _year;
int _month;
int _day;
public:
//多参数的构造函数
Date(int year, int month=1, int day=1)
:_year(year)
, _month(month)
, _day(day)
{
}
};
int main()
{
Date d = 2000;
return 0;
}
监视窗口:
编译器在进行构造优化时,会尽可能地减少不必要的构造函数调用,以提高程序的性能和效率,具体来说,编译器会尝试进行以下优化:
复制消除(Copy elision):编译器会尝试避免不必要的拷贝构造函数调用,在某些情况下,编译器会直接将构造函数的参数传递给目标对象,而不是通过中间的临时对象进行拷贝,这样可以减少构造函数的调用次数;
返回值优化(Return value optimization):当函数返回一个临时对象时,编译器会尝试将临时对象直接构造在函数调用的目标对象中,而不是通过拷贝构造函数进行拷贝,这样可以减少拷贝构造函数的调用次数;
注:编译器的构造优化是基于编译器的实现和优化策略的,不同的编译器可能会有不同的优化行为;
//A类
class A
{
public:
//构造函数
A(int a = 0)
:_a(a)
{
cout << "调用构造函数" << endl;
}
//拷贝构造函数
A(const A& aa)
:_a(aa._a)
{
cout << "调用拷贝构造函数" << endl;
}
//析构函数
~A()
{
cout << "调用析构函数" << endl;
}
private:
int _a;
};
int main()
{
// 1.先用10构造一个临时对象 2.再用临时对象拷贝构造aa1
// 3.同一个表达式中,连续构造+构造/构造+拷贝构造/拷贝构造+拷贝构造会合二为一
A aa1 = 10;
return 0;
}
运行结果:
?a. 构造+构造---->构造
?b.构造+拷贝构造---->构造
?c.拷贝构造+拷贝构造---->拷贝构造
A Func()
{
A aa;
return aa;
}
int main()
{
// 拷贝构造+拷贝构造->拷贝构造
A aa1 = Func();//Func()函数运行到返回时先将aa拷贝构造到临时对象中,
//再将临时对象中拷贝构造到aa1中
return 0;
}
- 注:编译器优化时,无论a,b,c哪种情形,必须在同一表达式中执行;
A Func()
{
A aa;
return aa;
}
int main()
{
A aa2;
//aa2对象已经存在
//Func()函数运行到返回时先将aa拷贝构造到临时对象中,
//由于aa2对象已存在,再将临时对象中赋值拷贝到aa2中
aa2 = Func();
return 0;
}
运行结果: