C++入门--lesson4
目录:
1.再谈构造函数
2.Static成员变量及成员函数
3.友元
4.内部类
5.匿名对象
6.拷贝对象时编译器的优化
1.再谈构造函数
1.1构造函数的构成
其实构造函数分为几个部分。1.初始化列表 2.函数体
class rxj
{
public:rxj()//放置初始化列表的地方:_age(19)//初始化,就是以一个冒号开始,中间的每个成员都用
//逗号分开,并且成员的后面跟上一个括号,用来初始化该成员,_height(174),_name("rxj"){//函数体的部分}
private:int _age;string _name;int _height;
};
值得注意的是初始化列表的写法,1.以冒号开始 2.每个成员之间用逗号分开 3.成员的后面跟上一个括号,括号里面用于存放你要初始化的值
1.2构造函数的函数体赋值
函数体内赋值,也就是在构造函数的函数体内部{}中对每个变量进行赋值,所谓赋值,就是在这个变量生成以后对其进行的赋值,而不是一开始就进行初始化。所以可以把函数体内复制理解成对成员的二次加工。
1.3构造函数的初始化列表
初始化列表的作用就相当于是c++打的补丁,在类里面队成员变量声明的时候赋的值,但是两者之间还有一些不同。因为你在声明的时候赋的值不能接收外部传来的值。
class rxj
{
public:rxj(int age,int height,string name)//放置初始化列表的地方:_age(age)//初始化,就是以一个冒号开始,中间的每个成员都用
//逗号分开,并且成员的后面跟上一个括号,用来初始化该成员,_height(height),_name(name){//函数体的部分}
private:int _age;string _name;int _height;
};
初始化列表的意义是什么呢?如果成员变量有一个是引用/const修饰/没有默认构造函数的自定义类型变量。那么他们只能在初始化列表哪个部分进行初始化。(因为他们的特点就是在声明的时候必须有定义)
补充:一个类中的每一个成员变量都会走一次初始化列表,如果是自定义类型(其有默认构造函数),即使你没有在初始化列表处对其进行定义,编译器会自动调用它的默认构造函数。
这里就引申出一个问题:众所周知,当我们没有写构造函数时,编译器会自动生成一个默认构造函数,那个默认构造函数会自动调用自定义类型成员的默认构造函数。现在我们明明已经写了构造函数,系统在哪里又调用了这个自定义类型的构造函数呢?
我们要分清一个问题:在 C++ 中,如果你定义了一个默认构造函数(无论是否显式初始化列表),但没有在初始化列表中显式初始化某个自定义类型的成员变量,编译器会自动调用该自定义类型的默认构造函数。这是 C++ 的强制规则,这个与默认构造函数是否由你显式定义或编译器自动生成无关。
1.4成员变量的初始化顺序
成员变量在类中声明的次序就是其在初始化列表中初始化的顺序。初始化的顺序与初始化列表中的成员的先后顺序无关
一个例题:
class rxj
{
public:rxj(int r1):_r1(r1),_r2(_r1){cout << "_r1:" << _r1 << " _r2:" << _r2 << endl;}
private:int _r2;int _r1;
};int main()
{rxj r1(4);
}
1.5explicit关键字
为了讲解explicit关键字,我们要引入一个东西叫做类型转换。构造函数不仅可以参与初始化对象,对于只具有单个参数或者除了第一个参数其余都有默认值的构造函数来说,还具有类型转换的作用!!!我们可以先回顾一下强制类型转换,
double x = 3.14;int y = int(x);printf("%d", y);
为什么说只有一个参数或者除了第一个参数其余都有默认值的构造函数可以强制类型转换呢?其实就是为了这样能够实现赋值
class rxj
{
public:rxj(int a){_a = a;}void print(){cout << _a << endl;}
private:int _a;
};int main()
{rxj R1 = 4;R1.print();return 0;
}
所谓的类型转换就是把对应的类型作为对象的参数创建了一个对象。
值得注意的是,类型转换的底层逻辑是先构造函数,然后再进行拷贝构造。 了解这个可以让我们对栈帧的开辟和销毁有个更深入的理解。
2.Static成员变量及成员函数
让我们由一个问题入手来引出这个知识点的讲解:实现一个类,并且计算程序中出现了多少个类的对象?
有的人说可以使用全局变量。这其实是一个好方法
int count = 0;
class rxj
{
public:rxj(){++::count;}rxj(const rxj& a){++::count;}~rxj(){--::count;}
private:
};
全局变量有一个缺点就是没有封装起来,缺少了一些安全性。在这里我们就要引出静态成员变量
静态成员变量的几个规则:
1.静态成员变量的定义必须在类外面,并且不需要再次添加Static这个关键字
2.静态成员变量仍然受访问限制付的的限制(但是公有的静态成员变量可以通过类名+作用域限定符直接访问)
3.静态成员变量和函数都是所有类对象所共有的,不属于某一个具体的对象。
4.与第二点可以合并,静态成员函数和变量都可以通过类名+访问限定符去访问静态成员。
5.静态成员函数没有this指针,这也就注定了他不能访问非静态函数
6.与第二点合并,静态成员函数以及变量都受访问限定符的限制
接下来我们通过一个实例来演示一下静态成员变量(一般都放在private中,否则和全局变量无异)和静态成员函数的使用(一般与静态成员变量配套出现,目的是为了去读取私有的静态成员变量)
class rxj
{
public:rxj(){++count;cout << "rxj()" << endl;}rxj(const rxj& a){++count;cout << "rxj(cosnt rxj& a)" << endl;}~rxj(){--count;cout << "~rxj()" << endl;}static int Getcount()//函数内部没有隐含的this指针{cout << "count:" << count << endl;return count;}
private:static int count;//注意要在类外面定义
};int rxj::count = 0;rxj Func(rxj r1)//拷贝构造
{rxj r2 = r1;//拷贝构造cout <<"-----------" << endl;return r2;//拷贝构造
}int main()
{rxj r1;//默认构造rxj r2 = r1;//拷贝构造rxj r3;//默认构造rxj r4 = Func(r3);//rxj::Getcount();return 0;
}
3.友元
3.1友元函数
什么是友元呢?其实之前我们在讲赋值运算符重载的时候讲过,在重载运算符<<时,我们习惯上是把cout<<+对象,所以这个运算符重载就不能写在类的内部,我们只能写成全局函数,但是我们怎么访问类里面的元素呢?这里我们就可以利用友元或者公共成员函数。
1,利用友元
class rxj
{
public:friend ostream& operator << (ostream& cout, rxj& r1);rxj(){++count;cout << "rxj()" << endl;}rxj(const rxj& a){++count;cout << "rxj(cosnt rxj& a)" << endl;}~rxj(){--count;cout << "~rxj()" << endl;}static int Getcount()//函数内部没有隐含的this指针{cout << "count:" << count << endl;return count;}
private:static int count;//注意要在类外面定义int height = 174;int age = 19;
};
ostream& operator << (ostream& cout, rxj& r1)
{cout << r1.age << " " << r1.height << endl;return cout;
}int rxj::count = 0;int main()
{rxj r1;//默认构造cout << r1;return 0;
}
2.利用成员函数
class rxj
{
public:int Getheight(){return height;}int Getage(){return age;}friend ostream& operator << (ostream& cout, rxj& r1);rxj(){++count;cout << "rxj()" << endl;}rxj(const rxj& a){++count;cout << "rxj(cosnt rxj& a)" << endl;}~rxj(){--count;cout << "~rxj()" << endl;}static int Getcount()//函数内部没有隐含的this指针{cout << "count:" << count << endl;return count;}
private:static int count;//注意要在类外面定义int height = 174;int age = 19;
};
ostream& operator << (ostream& cout, rxj& r1)
{cout << r1.Getage() << " " << r1.Getheight() << endl;return cout;
}int rxj::count = 0;int main()
{rxj r1;//默认构造cout << r1;return 0;
}
注意:
1.友元函数只是一个声明,不受类访问限定符的限制
2.一个函数可以是多个类的友元
3.友元函数不能用const修饰
4.友元函数不是该类的成员函数,但是可以访问该类的私有和公有成员
3.2友元类
友元类的使用方法和友元函数类似,都是在另一个类中声明(声明在哪里无所谓),声明过后就可以访问对方的私有成员
class kym//
{
public:void Gte(rxj a1){cout << a1.age << endl;}
};class rxj
{friend kym;//进行友元类的声明
public:int Getheight(){return height;}int Getage(){return age;}friend ostream& operator << (ostream& cout, rxj& r1);rxj(){++count;cout << "rxj()" << endl;}rxj(const rxj& a){++count;cout << "rxj(cosnt rxj& a)" << endl;}~rxj(){--count;cout << "~rxj()" << endl;}static int Getcount()//函数内部没有隐含的this指针{cout << "count:" << count << endl;return count;}
private:static int count;//注意要在类外面定义int height = 174;int age = 19;
};
ostream& operator << (ostream& cout, rxj& r1)
{cout << r1.Getage() << " " << r1.Getheight() << endl;return cout;
}
4.内部类
定义:如果一个类定义在另一个类的内部,那么在里面的那个类就叫做内部类,外面的那个类就叫做外部类
内部类是外部类的 “成员”(所以它本身会受到访问限定符的限制),因此对外部类的所有成员有 “潜在访问权”(非静态需对象);但内部类自身的成员,仍受其自身限定符保护,外部类也不能随意访问。
补充:内部类只是一个声明,并不占据空间,我们在定义内部类的时候,要通过空间访问限定符::来创建,也就相当于是外部类的一个成员
5.匿名对象
匿名对象就是类名+(),这个括号用来传参,如果没有参数,那么这个就相当于是一个简易版的对象。他的生命周期只在本行!!!(但是如果被const引用,那么他的生命周期就会被拉长,这个结论适用于所有临时对象)所以比较适合即用即丢那种类型的
我们来举一个例子
class rxj
{
public:int Getheight(){return height;}int Getage(){return age;}friend ostream& operator << (ostream& cout,rxj& r1);rxj(int _height, int _age){++count;cout << "rxj()" << endl;age = _age;height = _height;}rxj(const rxj& a){++count;cout << "rxj(cosnt rxj& a)" << endl;}~rxj(){--count;cout << "~rxj()" << endl;}static int Getcount()//函数内部没有隐含的this指针{cout << "count:" << count << endl;return count;}void print(){cout << age << " " << height << endl;}
private:static int count;//注意要在类外面定义int height = 174;int age = 19;
};
ostream& operator << (ostream& cout,rxj& r1)
{cout << r1.Getage() << " " << r1.Getheight() << endl;return cout;
}int rxj::count = 0;int main()
{rxj(111, 222).print();return 0;
}
我们可以通过匿名创建一个对象然后使用里面的函数
6.拷贝对象时编译器的优化
有三个例子。
1.隐式类型转换的时候,编译器将构造函数+拷贝构造函数简化成了构造函数
2.调用函数的返回值进行拷贝构造:拷贝构造+拷贝构造=拷贝构造:
class rxj
{
public:rxj(){cout << "rxj()" << endl;}rxj(const rxj& a){cout << "rxj(cosnt rxj& a)" << endl;}
};
rxj Func()
{rxj r1;return r1;
}
int main()
{rxj r2 = Func();return 0;
}
不懂,但是被优化了
3.构造+拷贝构造->构造
void Func1(rxj r1)
{}
int main()
{rxj r2 = Func();Func1(rxj());//构造+拷贝构造->构造函数return 0;
}
4.构造+拷贝构造->构造
void Func1(rxj r1)
{}
int main()
{rxj r2 = Func();Func1(1);//构造+拷贝构造->构造函数return 0;
}
前提是rxj具有一个参数