当然这只是C++类和对象的入门
实际上类和对象还有很多内容没有进行讲解
就比如:多态和继承
这些会在博主进行深度自我提升后再来进行讲解。
之前在类和对象(中)曾讲过六个默认成员函数
其中就有构造函数,现在我们将在对构造函数进行深度介绍。
众所周知:
构造函数的作用是创建对象,并且对对象中的成员变量进行赋值
但是当我们接触多了以后就会发现其中有些特例不能用构造函数进行赋值。
例:int&以及const等
这两个都有一个共同特点:只能在定义的时候进行初始化
如果我们在函数内进行赋值就会报错
class test
{
public:
test(int b,int c)
{
_x = b;
_y = c;
}
private:
int& _x;
const int _y;
};
这样就会报错
这样就说明了一点:
对象的成员变量并不是在函数体内进行初始化的
class test
{
public:
test(int b,int c)
//初始化列表
:_x(b)
,_y(c)
//函数体
{
}
private:
int& _x;
const int _y;
};
这里就能发现初始化列表其实也是属于构造函数的一部分
仔细想想:
构造函数作用是创建对象的
而初始化列表的作用是对象的成员初始化
所以初始化列表是构造函数中的其中一部分也是理所应当。
1.因为初始化列表是初始化成员变量
所以不论有没有写初始化列表,都会走一遍初始化列表
所以既然程序都要自己走一遍初始化列表
所以尽量用初始化列表对成员变量进行初始化。
2.每个成员变量都只能在初始化列表中出现一次。
因为成员变量只能初始化一次,之后都是赋值。
所以之前在函数体内对成员变量都是赋值,不是初始化。
3.以下成员必须要在初始化列表进行初始化:
i.引用类成员变量
ii.const类成员变量
iii.没有默认构造函数的成员变量
class b
{
public:
//没有默认构造函数
b(int x)
{
_b = x;
}
private:
int _b;
};
class test
{
public:
test(int b,int c)
//初始化列表
:_x(b)
,_y(c)
,b1(b)
//函数体
{
}
private:
int& _x;
const int _y;
b b1;
};
大坑
4.这个算是c++中的大坑了
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
不知道咋回事,初始化列表不是在函数体内
所以不像函数体那样一行一行程序进行运行
而是成员变量的初始化顺序取决于声明顺序。
例:
class test
{
public:
test(int b,int c)
//初始化列表
:_x(b)//后初始化
,_y(c)//先初始化
{}
private:k
//取决于这里的声明
const int _y;
int& _x;
};
5.成员变量声明处给的缺省值,实际上是给初始化列表准备的。
class test
{
public:
test()
{
}
private:k
//如果没有初始化列表
//或者成员没有在初始化列表给值
//这里会自己在初始化列表用缺省值初始化
int _y=10;
int _x=10;
};
影视类型转换可以说是老朋友了
以前还特意除了一期int和char类型的转换
C语言里面的内置类型的类型转换都基本换汤不换药
基本都是中间有一个常性变量来存储13,然后赋值给a1
这个中间量的类型取决于最后要转变的类型
就比如这里是要把int转化为double
这个中间变量类型就是 const double
现在我们在C++篇章,还是在类和对象
我们当然要更上一个台阶,介绍的是:内置类型向自定义类型的转换
class test
{
public:
test(int x)
{
_x = x;
}
test(const test& t1)
{
_x = t1._x;
}
int getx()
{
return _x;
}
private:
int _x;
};
int main()
{
test t1 = 13;
cout << t1.getx() << endl;
}
这里我们来刨析一下过程:
这里是标准的转化过程
但是连续构造两次过于浪费,所以一般的比较新的编译器都会自行优化
这里我们可以来测试一下
class test
{
public:
test(int x)
{
_x = x;
cout << "默认构造" << endl;
}
test(const test& t1)
{
_x = t1._x;
cout << "拷贝构造" << endl;
}
private:
int _x;
};
int main()
{
test t1 = 13;
}
这里就发现vs2022已经优化掉了拷贝构造这个步骤。
如果不想产生这个隐式转换的效果,就可以添加上这个关键字进行限制
explicit test(int x)
{
_x = x;
cout << "默认构造" << endl;
}
友元是为了让一些函数和类能够访问类中的私有成员变量
但是不会成为类的成员函数或者内部类。
就好像到朋友家做客,可以吃吃喝喝,但是并没有成为一家人
顾名思义,友元函数就是一个类中的友元函数。
这里就来个流提取重载来引出友元函数
在类和对象(中),我们讲了操作符重载
这里就是要补一下流提取的重载
class Date
{
public:
Date(int year, int month, int day);
//流提取的重载
ostream& operator<< (ostream& out);
private:
int _year=2023;
int _month=1;
int _day=1;
};
这里先随便手搓一个Date类
接下来就是重载
//返回值 重载 形参
ostream& Date::operator<<(ostream& out)
{
out << _year <<"年" << _month <<"月" << _day << "日" <<endl;
//返回是为了连续<<的操作
return out;
}
重载不难
但是你用的时候会发现一个严重的问题
Date d1(1,1,1);
cout<<d1;
这样是行不通的
因为我们写的是成员函数
成员函数是自带this指针的,所以第一个参数是d1,第二个参数才是out
并且函数重载的操作符的使用顺序是参照形参的声明顺序的
就比如说
operator*(Date d1,int x)
{
}
这个重载操作符在使用的时候就要把d1放在第一个位置,x放在第二个顺序
d1*x。
这个时候在看流提取的重载
因为第一个的位置已经默认被this指针抢走了
所以我们的out变量只能放在第二个位置
就导致:
d1<
这样才是正确的使用顺序
但这样用毕竟不符合我们平常的习惯,看着也不顺眼
所以如果想要变成cout<
ostream& operator<<(ostream& out, const Date& d1)
这样的声明方式,将out类放到第一个形参位置,Date放到第二个形参位置
但是成员函数自带this指针,所以就要写到全局变量中。
ostream& operator<<(ostream& out, const Date& d1)
{
out << d1._year << "年" << d1._month << "月" << d1._day << "日" << endl;
return out;
}
全局函数写完后,你会发现一个重要问题:
那就是不能访问Date类的成员变量
这个时候就轮到友元函数登场了
class Date
{
//友元声明
friend ostream & operator<<(ostream& out, const Date& d1);
public:
Date(int year, int month, int day);
private:
int _year=2023;
int _month=1;
int _day=1;
};
在Date中加入这么一句声明,就能使<<重载操作符函数访问Date的成员变量了
友元类其实和友元函数没什么区别
就是友元类能使用类的成员变量
class Date
{
//这里声明后 test类可以随意访问Date类的成员变量。
friend class test;
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year = 2023;
int _month = 1;
int _day = 1;
};
class test
{
public:
//可以随意访问
int getdate(Date& d)
{
return d._year;
}
};
计算一个类创建了几个类对象
以我们现在的知识一般都会这么来实现吧:
int a = 0;
class A
{
public:
A()
{
a++;
}
~A()
{
a--;
}
private:
int _a;
};
A a1;
int main()
{
A a2;
cout << a << endl;
A a3;
cout << a << endl;
}
创建一个全局变量来进行计算。
但是我们都知道全局变量有很多不妥的地方
没有封装特别容易就被别人拿来随便改变
类的成员是进行了封装的。
但我们又不能找类中的成员变量
因为类中的成员变量都是一个对象独有一个的,不能来统计计算一个类有多少个类对象
现在我们就需要一个
1.被封装的,最好是类中的成员变量
2.能独立于一个类的所有对象之外的,不被单个对象占有
这样完美符合我们要求的就是静态成员变量
class A
{
public:
A()
{
a++;
}
~A()
{
a--;
}
static int a;
private:
int _a;
};
int A::a = 0;
A a1;
int main()
{
A a2;
A a3;
cout << A::a << endl;
}
1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
因为静态成员变量是属于整个类的,被所有对象共享
所以,不能在构造函数中定义,这样的话每个函数走的话都要重新定义一次了
3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
cout << A::a << endl;
4. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
静态成员函数和静态成员变量一样,都是一个类中共有的公用成员。
它既然是一个类的公用函数那能说明什么?
说明它没有this指针
因为它不属于任何一个单独对象,所以它不会接受单独一个对象的this指针。
那这里就有问题了,如果想在函数中调用函数会是什么情况:
这里结果可以认为是:
静态成员函数有的,成员函数都有
静态成员函数没有的,成员函数也还有
1.静态调用成员函数:
这显然是不可以的
因为成员函数是每个对象的单独函数,所以需要this指针
但是静态函数没有this指针,所以显然是不可以调用的。
class A
{
public:
static int happy()
{
sad();
}
int sad()
{
}
};
2.成员函数调用静态
class A
{
public:
static int happy()
{
}
int sad()
{
happy();
}
};
这样显然是可以的。
因为静态成员没有具备啥要求是成员函数不能是实现的。
说简单点:就是在类中的类
class A
{
public:
A(int y)
{
}
class B
{
public:
B(int x)
{
}
private:
int _b;
};
private:
int _a;
};
int main()
{
A::B b1(1);
}
在这里B就是A的内部类
注意点
1.内部类和外部类是两个独立的类,并不有从属关系,只是内部类定义在外部类里而已
2.外部类不能随便访问内部类的成员变量
3.但是内部类可以随便访问外部类的成员变量(可以认为内部类是外部类的友元类)
4.sizeof(外部类) 还是等于外部类的大小,并不计算内部类的大小