潍坊网站建设培训,网站改版设计流程,深圳市住建工程交易,公司注册地址怎么查2.5 取地址及const取地址操作符重载
2.5.1 const成员 在介绍取地址和const取地址操作符重载前#xff0c;需要先介绍一下const成员作为基础。在C中#xff0c;我们将使用const修饰的成员函数称为const成员函数。const看似修饰成员函数#xff0c;实际则是对成员函数隐含的t…2.5 取地址及const取地址操作符重载
2.5.1 const成员 在介绍取地址和const取地址操作符重载前需要先介绍一下const成员作为基础。在C中我们将使用const修饰的成员函数称为const成员函数。const看似修饰成员函数实际则是对成员函数隐含的this指针进行修饰被const修饰的this指针就不可以解引用改变值了即该成员函数不能对类的成员做修改。 由于this指针不能显式写出所以const就加在函数之后来表示修饰。
void Print2() const
{cout _year endl;
}
//void Print2(const Date* this)
//{
// cout _year endl;
//} const成员的说明
①因为函数内修改成员变量都是通过this指针完成的所以当this被加上const限制就说明该函数无法对成员变量做任何修改。
②注意调用传参情况下权限问题。权限的缩小和平移都是允许的而权限的放大由const变为非const是不被允许的。所以const修饰的对象不可以调用非const修饰的成员函数。同理const修饰的成员函数内也不允许调用非const修饰的成员函数。
③成员函数是否需要const修饰取决于其是否需要在函数内进行写操作。如果成员函数涉及到成员变量的写操作则不可以加const修饰如果只涉及到成员变量的读访问则建议加const修饰。
class Date
{
private:int _year;
public:Date(int year){_year year;}//如果成员函数涉及到成员变量的写操作则不可以加const修饰
//如果成员函数只涉及到成员变量的读访问则建议加const修饰void Print1(){cout _year endl;this-Print2(); //非const修饰的成员函数调用const修饰成员函数this指针由非const变为const权限缩小}void Print2() const //const修饰this指针{cout _year endl;//this-Print1(); //error:const修饰的成员函数this指针是const调用非const修饰成员函数会使this指针权限放大}
};int main()
{const Date d1(2022);Date d2(2023);//d1.Print1(); //error:(d1)const Date* - (this)Date*权限的放大 d1.Print2(); // (d1)const Date* - (this)const Date*权限的平移 d2.Print1(); // (d2)Date* - (this)Date*权限的平移 d2.Print2(); // (d2)Date* - (this)Date*权限的缩小
}
2.5.2 取地址及const取地址操作符重载 取地址操作符重载和const取地址操作符重载是类的6个默认成员函数的最后两个因为重载了取地址操作符所以其功能就是取出对象的地址。 因为是默认成员函数所以当我们不实现时编译器会自动生成。编译器自动生成的取地址操作符重载也是返回对象的地址。所以一般情况下没有对取地址操作符有特殊要求那么可以不用写使用编译器默认生成的即可。
class Date
{
public://取地址及const取地址操作符重载//一般不用自己写采取编译器默认生成的编译器生成的默认成员函数也是返回对象变量的地址//特殊情况下才需要自己生成Date* operator(){return this;}const Date* operator()const{return this;}
};
int main()
{Date d1;Date d2;cout d1 endl;cout d2 endl;return 0;
}
到此为止我们便对六个默认成员函数进行了细致的解析当然还有许多我们没有涉及到的细节点。在对类和成员函数有了大致认识后接下来我们再来深入了解一下类中所涉及到的细节。
3. 构造函数——初始化列表 在最初介绍构造函数的时候我们曾说对象在定义时会自动调用构造函数并在构造函数内完成定义与初始化。这句话不能算是很精确因为成员变量的定义实际并不是在构造函数体内完成的而是在初始化列表中完成定义与初始化的。 初始化列表的说明
①初始化列表位于构造函数名之后以一个冒号开始接着是以逗号分隔的数据成员列表每个成员变量后使用放在括号中的初始值或表达式来初始化。
class Date
{
private:int _year;int _month;int _day;
public://初始化列表Date(int year,int month,int day):_year(year),_month(month),_day(day){}void Print(){cout _year - _month - _day endl;}
};
int main()
{Date d1(2003,12,4);d1.Print();
} ②首先理清定义发生的逻辑列在类中的成员变量属于成员变量的声明还没有定义。成员变量的定义发生在对象实例化后调用了对象的构造函数构造函数会先执行初始化列表再执行函数体。而成员变量的定义正是发生在初始化列表中。
③初始化列表是成员变量完成定义的地方所有成员变量无一例外都在也只能在初始化列表定义尽管有的成员变量没有在初始化列表显式写出定义和初始化依旧会发生。对于定义后初始化的值1首先取决于初始化列表中成员变量之后的括号中的值如果没有给出那么2考虑缺省值初始化列表初始化的缺省值在成员变量的声明处给出。如果以上两步均没有成功初始化那么便会3初始化为随机值。
④所有变量都在初始化列表完成了定义与初始化且在初始化列表每个成员变量只能出现一次只能定义并初始化一次。在构造函数体内部则是修改赋值的部分成员变量在此处可以多次修改赋值。
⑤需要区分清楚在构造函数中涉及到两组缺省值。第一组是构造函数的参数缺省值其作用就是参数缺省值的作用当实参数量不够时才采用该缺省值赋值给形参。第二组是声明处给定的缺省值其作用是为初始化列表提供初始化的缺省值如果初始化列表没有给定初始化值便会使用该缺省值初始化。
class Date
{
private://此部分属于成员变量的声明还没有定义//此处所写的缺省值是提供给初始化列表的是成员变量定义的初始化值int _year 1999;int _month 10;int _day;const int _n;public://在构造函数中初始化列表部分才是每个成员变量定义初始化的位置Date(int year,int month,int day) //构造函数参数缺省值和声明给定初始化缺省值不冲突前者在调用构造函数传参缺省时才会启用后者在初始化列表缺省时启用//此处虽然看似只定义了_n和_month并初始化实际上所以成员变量都在此定义了:_n(1),_month(1) //没有给出_year成员的初始化值采取缺省值初始化为1999//给出了_month成员的初始化值直接初始化为1//没有给出_day成员的初始化值且_day成员没有缺省值则初始化为随机值//给出了_n成员的初始化值直接初始化为1//在构造函数的函数体内就不再是定义初始化了而是赋值修改{_year year;_month month;_day day;//_n 1; //error:定义不发生在构造函数函数体所以此处认为是赋值给const赋值所以报错}void Print(){cout _year - _month - _day endl;}
};
int main()
{//在对象实例化的时候定义了对象整体而对象的成员定义则要通过调用构造函数来定义Date d1(2003,12,4);d1.Print();
}
⑥在定义成员变量时建议能用初始化列表就用初始化列表因为初始化列表是不可避免会执行的所以建议尽量多使用初始化列表。
⑦在定义时必须初始化的变量必须使用初始化列表与其说必须使用不如说必须显示写出因为要被人为初始化就必须显式写出const修饰的成员变量引用成员变量自定义类型成员且不存在默认构造函数时。这三种都是需要在定义时就完成初始化的所以需要必须要在初始化列表显式写出。 重点解释一下不存在默认构造函数的自定义成员。 首先明确默认构造函数的定义不需要传参的构造函数就是默认构造函数编译器自动生成的构造函数的类型就是默认构造函数。 如果类的成员变量中有自定义类型成员那么在初始化列表定义时当定义到该自定义类型成员时会调用它的构造函数。如果该自定义类型存在默认构造函数那么就说明无需传参也可以调用构造函数因此不需要手动完成可以不在初始化列表显式定义。但是如果自定义类型中没有默认构造函数即构造函数需要手动传参那么就必须在初始化列表显式定义了。 总结一下自定义对象是一定会在初始化列表定义的初始化时调用其自身的构造函数。而是否存在默认构造函数只决定是否允许不在初始化列表显式写出定义。
class A
{
public:A(int a){}
};
class C
{
public:C(int a 1){}
};
class B
{
private:int _b;const int _n;int _ref;A _aa;C _cc;
public:B(int b, int n, int ref):_b(b),_n(n),_ref(ref),_aa(3) //A类因为实现了需要传参的构造函数所以对A类对象初始化需要手动传参所以需要在初始化列表显式定义//C类因为存在默认构造函数不需要传参的构造函数所以无需手动操作可以不在初始化列表显式定义//类对象是一定会在初始化列表定义的初始化时调用其自身的构造函数//而是否存在默认构造函数只决定是否允许不在初始化列表显式写出定义{}
};
int main()
{int num;B b1(1, 2, num);
} ⑧初始化列表的初始化值只限制是可以赋值的右值即可。
class A
{
private:int* p nullptr;
public:A():p((int*)malloc(sizeof(int) * 10))//x(y) 相当于是 xy同理此处是p(int*)malloc(sizeof(int) * 10)括号内应该是一个值缺省值同理{if (p NULL){perror(malloc fail);}}
};
⑨以下介绍另一个构造函数的特性单参数或只有第一个参数无缺省值构造函数支持隐式类型转换。 如果类对象的构造函数仅有一个参数那么就支持隐式类型转换。例如如下代码当写下A a2 2; 的代码后由于A类的构造函数是单参数所以赋值号右侧的2就会发生隐式类型转换将类型变为A类的对象类型这和使用强制类型转换 (A)2; 的效果一致。此时2变成了A类的临时对象因为发生了隐式类型转换所以是临时的在赋值给a2就是调用了拷贝构造将该临时对象拷贝给了a2。
class A
{
private:int _a;
public:A(int a):_a(a){}
};
class B
{
private:A _aa 3;//由于单参数构造函数支持隐式类型转换便可以顺利给出类对象缺省值
};
int main()
{A a1(1);//单参数或只有第一个参数无缺省值构造函数支持隐式类型转换A a2 2; //A a2 (A)2;//构造函数参数需要int所以实际是2先构造了一个A类的临时对象再将这个临时对象通过调用拷贝构造给a2//实际执行时同一个表达式连续步骤的构造编译器会进行优化此处将最后的拷贝构造优化掉了const A ra1 3; //3隐式转换为A类型的临时对象具有常性从而可以被常引用引用//A ra2 4; //error:发生隐式转换后的临时对象具有常性权限缩小
} 在C11中规定支持了多参数隐式类型转换。
class A
{
public:A(int a,int b){}
};
int main()
{A a1 { 1,2 };//多参数隐式类型转换 C11标准规定
}
⑩explicit关键字修饰构造函数可以禁止上一点中介绍的类型转换。
class A
{
public:A(int a,int b){}
};
int main()
{A a1 { 1,2 };//多参数隐式类型转换 C11标准规定
}
⑪构造函数初始化列表中定义成员变量的顺序是按照成员变量声明的顺序进行的而不是初始化列表的顺序。
class A
{
private:int _a2;int _a1;
public:A(int a):_a1(a), _a2(_a1){}void Print() {cout _a1 _a2 endl;}
};
int main() {A aa(1);aa.Print(); //输出1 随机值//规定初始化顺序按照声明顺序初始化并不是按照初始化列表顺序
}