文章目录
- 一、初始化列表
- 1、初始化列表的使用
- 2、必须使用初始化列表来初始化的成员
- 二、类型转换
- 1、内置类型转换自定义类型
- 2、自定义类型转换自定义类型
- 三、静态成员变量(static)
- 1、static修饰成员变量
- 2、静态成员函数
- 四、友元
- 五、类里面再定义类
- 六、匿名对象
- 1、匿名对象的使用
- 2、延长匿名对象的生命周期
- 七、C++动态内存开辟
- 1、new操作符开辟操作符
- 2、delete操作符
- 3、new跟malloc的区别
- 4、delete跟free的区别
- 5、定位new初始化
一、初始化列表
1、初始化列表的使用
- 在构造函数体外使用冒号开始,逗号隔开,括号里面是初始化的值或一个表达式。
- 每个成员只能在初始化列表初始化一次。
//在构造函数体内初始化
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//初始化列表初始化
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
}
上面这个日期类的初始化效果是一样的。
2、必须使用初始化列表来初始化的成员
- 没有默认构造时的类类型成员必须使用初始化列表初始化
class stack
{
public:
//构造函数
//但没有缺省值,没有默认构造
stack(int n)
{
_a = (int*)malloc(sizeof(int) * n);
_top = 0;
_capacity = n;
}
private:
int* _a;
int _top;
int _capacity;
};
//用两个栈实现一个队列
class Myqueue
{
public:
//自定义类型没有默认构造使用初始化列表初始化
Myqueue(int n = 10)
:_q1(n)
, _q2(n)
{
}
private:
stack _q1;
stack _q2;
};
- const 修饰的成员只能在初始化列表初始化
- 引用成员变量只能在初始化列表初始化
class Myqueue
{
public:
//自定义类型没有默认构造使用初始化列表初始化
Myqueue(int& c, int n = 10)
:_q1(n)
, _q2(n)
, x(10)
, a(c)
{
}
private:
stack _q1;
stack _q2;
const int x;
int& a;
};
- C++11支持在成员申明时给缺省值,这个缺省值是给初始化列表用的。
class Date
{
public:
private:
int _year = 1;//给初始化列表缺省值
int _month = 1;
int _day = 1;
};
- 初始化列表是按照成员声明的顺序初始化,不是按初始化列表的顺序初始化。
举例:
class A
{
public:
A(int n)
:_a1(n)
,_a2(_a1)
{
}
void print()
{
std::cout << _a1 << ' ' << _a2 << std::endl;
}
private:
int _a2 = 2;
int _a1 = 1;
};
int main()
{
A g(3);
g.print();
return 0;
}
输出结果:先初始_a2,用_a1给_a2初始化,此时_a1的值随机。
然后_a1再初始化为3。
二、类型转换
1、内置类型转换自定义类型
C++支持内置类型和类类型的相互转换
class A
{
public:
A(int n)
:_a1(n)
,_a2(_a1)
{
}
void print()
{
std::cout << _a1 << ' ' << _a2 << std::endl;
}
private:
int _a2 = 2;
int _a1 = 1;
};
int main()
{
A aa1 = 1;
aa1.print();
return 0;
}
用整形 1 创建了一个临时的A构造函数,拷贝构造给了aa1
但因为构造加拷贝构造太浪费了,就直接优化为直接构造
看上图,编译器只执行了一次构造函数。
- 如果不想让隐式类型发生转换可以在前面加 explicit
explicit A(int n)
:_a1(n)
,_a2(_a1)
{
std::cout << "A(int n)" << std::endl;
}
- 当有多个参数转换时,用大括号括起来
class A
{
public:
A(int n, int m)
:_a1(n)
, _a2(m)
{
std::cout << "A(int n, int m)" << std::endl;
}
void print()
{
std::cout << _a1 << ' ' << _a2 << std::endl;
}
private:
int _a2 = 2;
int _a1 = 1;
};
int main()
{
A aa2 = { 1,1 };
aa2.print();
return 0;
}
2、自定义类型转换自定义类型
只要类型直接有关联就可以转换,这个关联需要借助构造函数。
class A
{
public:
A(int n, int m)
:_a1(n)
, _a2(m)
{
}
//访问成员
int Get()const
{
return _a1 + _a2;
}
private:
int _a2 = 2;
int _a1 = 1;
};
class B
{
public:
B(const A& aa)
:_b(aa.Get())
{
}
void print()const
{
std::cout << _b << std::endl;
}
private:
int _b;
};
int main()
{
A aa2 = { 1,1 };
B bb1 = aa2;
bb1.print();
return 0;
}
需要注意的是需要借助Get()成员函数来访问私有的成员。
三、静态成员变量(static)
1、static修饰成员变量
- 静态成员变量初始化在类外面。
- 静态成员不只属于一个类的对象,而是属于所以类的对象,存储在静态区。
class F
{
public:
int Get()
{
return _a;
}
private:
static int _a;//在类里面声明
};
int F::_a = 0;//在类外面初始化
int main()
{
F f1;
std::cout << f1.Get() << std::endl;
return 0;
}
private限制的是类外面访问不到,提供一个类成员函数Get()就可以访问类成员变量了,但前提是创建了类的对象,通过对象调用函数。
2、静态成员函数
因为调用静态成员需要创建对象,所以为了能直接访问静态成员就有了静态成员函数。
- 静态成员函数没有隐含this指针。
- 静态成员函数只能访问静态成员变量。
class F
{
public:
F()
{
++_a;
}
~F()
{
--_a;
}
static int Get()
{
return _a;
}
private:
static int _a;//在类里面声明
};
int F::_a = 0;//在类外面初始化
void Fcount()
{
std::cout << F::Get() << std::endl;
}
int main()
{
F f1;
F f2;
Fcount();
return 0;
}
四、友元
- 友元是一种突破封装的函数,在类里面使用friend关键词加上允许访问私有或保护的函数声明,可以让类外的函数访问私有或包含的类成员变量。
class Date
{
public:
//友元申明
friend std::ostream& operator<<(std::ostream& out, const Date& d);
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
std::ostream& operator<<(std::ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day <<"日" << std::endl;
return out;
}
int main()
{
Date d1(2023, 3, 4);
std::cout << d1;
return 0;
}
- 成员函数也可以是别人的友元,友元不连续,例如:A是B的友元,B是C的友元,但A不是C的友元。
一个函数可以是多个类的友元但需要以以声明。
五、类里面再定义类
在类里面定义一个类
class A
{
private:
int _n;
static int _a;
public:
class B
{
private:
int b;
public:
};
};
int main()
{
int size = sizeof(A);
cout << size << endl;
return 0;
}
可以观察占用空间大小,发现内部类没有实例化,就相当于只是一个定义没有创建对象,而静态成员变量存储在静态区,程序完全结束才销毁。
- 内部类是一个独立的类,相较与全局类区别是受外部类域的限制,也受访问限定符限制。
如果想创建B类,因为受到A类域的限制所以要指定类域后创建
- 内部类默认是外部类的友元
class A
{
private:
int _n;
static int _a;
public:
class B
{
private:
int b;
public:
void fun(const A& h)
{
cout << _a << endl;
cout << h._n << endl;
}
};
};
内部类直接访问外部静态成员变量,如果不是静态,指定对象也可以访问外部类的私有成员变量
六、匿名对象
1、匿名对象的使用
平时类创建的对象都是有名字的,没有名字的对象是匿名对象。
class A
{
private:
int _n;
public:
A(int n = 10)
:_n(n)
{
cout << "A(int n)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
//有名对象
A a(2);
A a2;
//匿名对象
A(2);
A();
return 0;
}
- 匿名对象在没有参数时也需要空括号
- 匿名对象的声明周期只在当前这一行
在给自定义类型缺省值时使用匿名对象
void fun(A aa = A())
{
}
2、延长匿名对象的生命周期
- 匿名对象可以应用,但匿名对象跟临时对象一样具有常性,需要加const修饰,从而延长了匿名对象的生命周期。
class A
{
private:
int _n;
public:
A(int n = 10)
:_n(n)
{
cout << "A(int n)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
void fun(A aa = A())
{
}
int main()
{
//有名对象
A a(2);
A a2;
//匿名对象
A(2);
A();
const A& a3 = A();//延长生命周期
return 0;
}
七、C++动态内存开辟
1、new操作符开辟操作符
使用方法:
如果开辟多个同类型空间,相数组那样的,只是存储在堆上
int main()
{
//开辟一个int类型大小的空间
int* ptr = new int;
double* ptr1 = new double;
//开辟并初始化
int* ptr2 = new int(3);
double* ptr3 = new double(3.2);
cout << *ptr << endl << *ptr1<<endl << *ptr2 << endl << *ptr3 << endl;
//开辟多个数据
int* ptr4 = new int[10];//都没初始化值随机
//开辟多个并初始化
int* ptr5 = new int[10] {1, 2, 3};//按顺序初始化没初始化的值为0
cout << ptr4[3] << endl ;
return 0;
}
2、delete操作符
功能是free()就是释放掉动态内存申请的空间,区别是更方便
释放一个对象:
int main()
{
//开辟一个int类型大小的空间
int* ptr = new int;
delete ptr;
return 0;
}
释放多个对象:
int main()
{
int* ptr4 = new int[10];//都没初始化值随机
delete[] ptr4;
return 0;
}
3、new跟malloc的区别
除了方便写法简单外,在用于开辟类对象空间时有很大区别
class A
{
public:
A(int n = 10)
{
_n = n;
}
private:
int _n;
};
int main()
{
A* ptr1 = (A*)malloc(sizeof(A));
A* ptr2 = new A;
return 0;
}
通过监视功能可以看出主要区别是new开辟对象时会自动调用拷贝构造
也可以传参调用构造函数
A* ptr3 = new A(100);
- 如果没有默认构造就会报错
4、delete跟free的区别
在堆类创建的对象进行释放时,有所不同
delete会调用析构函数再释放空间
5、定位new初始化
因为C++不支持显示调用构造函数,但是也可以通过new来调用
#include <iostream>
using namespace std;
class A
{
public:
A(int n = 10)
:_n(n)
{
cout << "A(int n)" << endl;
}
private:
int _n;
};
int main()
{
A* ptr = (A*)operator new(sizeof(A));
new(ptr)A(20);//调用拷贝构造
return 0;
}
new(地址)类型(初始化的值),不传初始的值会调用默认构造
析构函数支持显示调用
#include <iostream>
using namespace std;
class A
{
public:
A(int n = 10)
:_n(n)
{
cout << "A(int n)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _n;
};
int main()
{
A* ptr = (A*)operator new(sizeof(A));
new(ptr)A;//调用拷贝构造
ptr->~A();
operator delete(ptr);
return 0;
}