有人找做网站的,wordpress 随机显示,网站英文版是怎么做的,网站制作与网站建设实际报告前言#xff1a; 本篇知识点#xff1a;初始化列表、explicit关键字、static成员、友元、内部类、匿名对象、编译器的优化 专栏#xff1a;C初阶 目录
再谈构造函数
1️⃣构造函数体赋值
2️⃣初始化列表
explicit关键字
static成员
1.static概念
2.static特性
面试…前言 本篇知识点初始化列表、explicit关键字、static成员、友元、内部类、匿名对象、编译器的优化 专栏C初阶 目录
再谈构造函数
1️⃣构造函数体赋值
2️⃣初始化列表
explicit关键字
static成员
1.static概念
2.static特性
面试题
友元
友元函数
友元类
内部类
内部类概念
优化面试题
匿名对象 匿名对象和有名对象
拷贝对象时的一些编译器优化
知识点回顾
示例(包含讲解)
传值传参和传值返回
构造拷贝构造
连续的拷贝构造
拷贝构造赋值重载(无法优化)
再次理解类和对象 再谈构造函数 对于MyQueue 不需要写它的构造函数编译器自动生成会调用它的默认构造。 但是如果Stack类不提供默认构造给你那就得实现显示调用该怎么办呢 有两种办法。 1️⃣构造函数体赋值
在创建对象时编译器通过调用 构造函数给对象中各个成员变量一个合适的初始值。 代码示例 class Date
{
public:Date(int year, int month, int day){_year year;_month month;_day day;}
private:int _year;int _month;int _day;
}; 虽然上述构造函数调用之后对象中已经有了一个初始值但是 不能将其称为对对象中成员变量的初始化构造函数体中的语句只能将其称为 赋初值而不能称作 初始化。 因为初始化只能初始化一次而构造函数体内可以多次 赋值 。 如下例子 class Date
{
public:Date(int year2024, int month1, int day1)//构造函数初始化,只能初始化一次//赋值{_year year;//可以多次赋值_year 2023; _year 2021; //..._month month;_day day;}
private:int _year;int _month;int _day;
};2️⃣初始化列表 初始化列表以一个 冒号 开始接着是一个以 逗号 分隔的数据成员列表每个成员变量后面跟 一个放在括号中的初始值或表达式。 代码如下 #includeiostream
using namespace std;
class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}void Print(){cout _year / _month / _day;}
private:int _year;int _month;int _day;
};
int main()
{ Date d1(2024, 1, 1);d1.Print();return 0;
} 执行如下 【注意】 1. 每个成员变量在初始化列表中 只能出现一次 ( 初始化只能初始化一次 ) 2. 类中包含以下成员 必须放在初始化列表 位置进行初始化 引用成员变量 const成员变量 自定义类型成员(且该类没有默认构造函数时) 其实可以这样理解 代码如下 class Date
{
public://初始化列表是每个成员定义的地方//不管你写不写每个成员都要走初始化列表Date(int year, int month, int day, int i):_year(year), _month(month), _a(i), _refi(i),_x(100)//显示给值了{//赋值//_day day;}void func(){_refi;_refi;}//private下面如果成员变量右边给了值都叫做缺省值
private:int _year;//每个成员声明int _month;int _day;//C11支持给缺省值这个缺省值给初始化列表//如果初始化列表没有显示给值就用这个缺省值//必须定义时初始化,也就是说以下这三个成员变量必须出现在初始化列表const int _x1;//如果显示给值了就不用这个缺省值int _refi;A _a;
};
//能用初始化列表就用初始化列表初始化
//有些场景还是需要初始化列表和函数体混着用
int main()
{int n 0;Date d1(2023, 7, 28, n);d1.func();cout n endl;return 0;
} 执行对_refi就是对着n操作 3. 尽量使用初始化列表初始化因为不管你是否使用初始化列表 对于自定义类型成员变量 一定会先使用初始化列表初始化。 #includeiostream
using namespace std;
class Time
{
public:Time(int hour 0):_hour(hour){cout Time() endl;}
private: int _hour;
};
class Date
{
public:Date(int day){}
private:int _day;Time _t;
};
int main()
{Date d(1);
} 执行 4. 成员变量在类中 声明次序 就是其在初始化列表中的 初始化顺序 与其在初始化列表中的先后次序无关 来做一道题 explicit关键字 构造函数不仅可以构造与初始化对象对于单个参数的 构造函数还具有类型转换的作用。 class A
{
public://explicit A(int i)A(int i):_a(i){cout A(int i):i endl;}A(const A aa):_a(aa._a){cout A(const aa) endl;}~A(){cout ~A() endl;}
private:int _a;
};
struct SeqList
{
public:void PushBack(const A x){//...扩容_a[_size] x;}size_t size() const{return _size;}//读const A operator[](size_t i)const{assert(i _size);return _a[i];}//读/写A operator[](size_t i){assert(i _size);return _a[i];}
private://C11A* _a (A*)malloc(sizeof(A) * 10);size_t _size 0;size_t _capacity 0;
};
int main()
{A aa1(1);A aa2 2;return 0;
} 经过编译器优化之后以下的两个代码是等价的 A aa1(2);//直接构造 A aa1 2解析 在早期的编译器中当遇到下面的一行代码时,会处理成用2调用A构造函数生成一个临时对象(tmp),再用这个对象(tmp)去拷贝构造aa1 A aa1 2;//先构造再拷贝构造上面的代码等价于下面这两步: A tmp(2); A aa2(tmp); 但是C支持单参数构造函数的隐式类型转换 编译器会再优化优化用2直接构造,所以当我们遇到像A aa1 2的式子时实际上编译器已经转换成了A aa1(2)这就叫隐式类型转换 同时在c中,不想让隐式类型发生就在构造函数前面加个explicit 因为explicit修饰构造函数禁止了单参构造函数类型转换的作用 在C语言中我们也讲了隐式类型转换无论是值拷贝还是说加了引用的都会生成临时变量的。 static成员
1.static概念 声明为static的类成员称为类的静态成员用static修饰的成员变量称之为静态成员变量 static修饰的成员函数称之为静态成员函数。 静态成员变量一定要在类外进行初始化 2.static特性 静态成员为所有类对象所共享不属于某个具体的对象存放在静态区 代码如下 #includeiostream
using namespace std;
class A
{
public:A() {n;m;}A(const A t){n;m;}~A(){--m;}
private:int a;//4 bytestatic int n;static int m;
};
int main()
{cout sizeof(A) endl;return 0;
} 2.静态成员变量必须在类外定义定义时不添加static关键字类中只是声明 解析思路 当我们要计算A这个类累计创建了多少个对象(用n表示)正在使用的多少个对象(用m表示) 以之前的知识我们首先会在全局定义两个变量 经过以下代码验证之后我们发现如果定义全局的变量会被外面随意修改 此时的话我们试下把n和m定义在class A的private内但是这样每一个对象在定义的时候都会创建一个n和m此时n和m是每一个对象的成员了不是用来统计有几个对象明显不能这样定义。 这时候如果被static修饰,这两个成员变量就位于静态区了,叫做静态成员变量,需要注意的地方有 代码如下 class A
{
public:
private:int a;//4 bytestatic int n;//静态成员的声明static int m;//静态成员的声明
};
//在类外面定义
int A::n 0;
int A::m 0; 还有要注意的 3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员来访问 静态成员变量被public修饰时: 代码如下 includeiostream
using namespace std;
class A
{
public:A(){n;m;}A(const A t){n;m;}~A(){--m;}
//private:static int n;static int m;
};
// 静态成员变量的定义初始化
int A::n 0;
int A::m 0;int main()
{A aa1;cout A::n A::m endl;//1.通过类名突破类域进行访问cout aa1.n aa1.m endl; //2.通过类对象突破类域进行访问A* ptr NULL;cout ptr-n ptr-m;//3.通过空指针解引用成员突破类域
} 当静态成员变量被private修饰时: 我们当然可以定义被public修饰的成员函数,然后此时被static修饰的两个静态成员n和m通过创建对象aa1,接着aa1.Print()就可以打印出对象的个数 但如果我定义一个匿名对象接着调用Print函数这时候会多出一个n(累计创建的对象)干扰打印的逻辑了。 我们可以借鉴上面静态成员变量突破类域的方式引出我们静态成员函数的三种突破类域的方式 代码如下: #includeiostream
using namespace std;
class A
{
public:A(){cout A() endl;n;m;}A(const A t){n;m;}~A(){--m;}//静态成员函数的特点:没有this指针static int GetM(){return m;}static void Print(){//x//不能访问非静态因为没有thiscout m n endl;}
private://这样定义不行这样的话每一个对象都有一个n和m//int m;//int n;// 不符合题意因为这是来统计对象个数的//静态成员变量属于所有A对象,属于整个类//声明//累积创建了多少个对象static int n;//正在使用的还有多少个对象static int m;
};int A::n 0;
int A::m 0;int main()
{A aa;//三种突破类域的方式A::Print();//通过类名调用静态成员函数进行访问aa.Print();//通过实例化的对象调用成员函数进行访问A* ptr NULL;ptr-Print();//通过空指针调用静态成员函数进行访问return 0;
}4. 静态成员函数没有隐藏的this指针不能访问任何非静态成员 代码如下 //4. 静态成员函数没有隐藏的this指针不能访问任何非静态成员
#includeiostream
using namespace std;
class A
{
public:static void Print(){cout x endl;}
private:int x;//非静态成员变量static int a;//静态成员变量
};
int main()
{A x;x.Print();
} 5. 静态成员也是类的成员受public、protected、private 访问限定符的限制 【问题】 1. 静态成员函数可以调用非静态成员函数吗 答不能 静态成员函数没有隐藏的this指针不能访问任何非静态成员 2. 非静态成员函数可以调用类的静态成员函数吗 答可以。 因为静态成员函数和非静态成员函数都在类中在类中不受访问限定符的限制。 代码如下 #includeiostream
using namespace std;
class A
{
public:void Notstatic(){Print();//-------非静态调用调用静态 }//两种写法static void Print(){cout a endl;}/*static int Print(){cout a endl;return a;}*/
private:int x;//非静态成员变量static int a;//静态成员变量
};
int A::a 10;
int main()
{A x;x.Notstatic();
} 执行 面试题 1. 求 123...n 要求不能使用乘除法、 for 、 while 、 if 、 else 、 switch 、 case 等关键字及条件判 断语句: 求123...n_牛客题霸_牛客网 (nowcoder.com) #includeiostream
using namespace std;
class Sum//定义一个名为Sum的类
{
public:Sum()//构造函数当创建Sum对象时自动调用{_ret _i;// 每次构造函数被调用时将静态成员变量_i的当前值累加到静态成员变量_ret上_i; //紧接着递增静态成员变量_i的值}static int GetRet()//定义一个静态成员函数GetRet用于获取静态成员变量_ret的值{return _ret;//直接返回静态变量_ret的值}
private:
//定义两个静态私有成员变量
// 静态成员变量属于类不是某个对象所有而是所有对象共享并且在整个程序生命周期内只初始化一次static int _i;//初始化为1每次构造函数调用时递增static int _ret;// 初始化为0用于累计构造函数调用次数
};//对静态成员变量进行初始化(定义)
int Sum::_i 1;
int Sum::_ret 0;
//定义另一个名为Solution的类
class Solution {
public:// 定义成员函数Sum_Solution接收一个整数参数nint Sum_Solution(int n) {Sum a[n];return Sum::GetRet();}
}; 友元 友元提供了一种突破封装的方式有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。 友元分为友元函数和友元类 友元函数 说明 : 友元函数可访问类的私有和保护成员但不是类的成员函数 友元函数不能用const修饰 友元函数可以在类定义的任何地方声明不受类访问限定符限制 一个函数可以是多个类的友元函数 友元函数的调用与普通函数的调用原理相同 我们之前在【C初阶】第一站:C入门基础(上) -- 良心详解-CSDN博客
简单了解过关于cout(流插入)cin(流提取)的知识
现在我们来回顾一下
在之前的印象中当我们遇到关于内置类型(int,float,double等)可以直接使用cout和cin进行输出和输入 原因是什么呢通过查阅资料可以发现i 和 j 通过操作符重载间接实现了类似成员函数的功能。 cin是istream类型的对象cout是ostream类型的对象 在C中内置类型是直接支持cout流插入和cin流提取这并不是什么自动识别类型是运算符重载和函数重载罢了库里面支持把内置类型作为成员函数重载了 这时候我们创建Date类的一个自定义类型的对象使用cout和cin输出和输入会发现编译错误 我们可以看到隐含的this指针占据着这个流插入成员函数的第一个参数的位置与main函数内调用的位置不相符cout是ostream类型的对象但是到了成员函数第一个位置是Date*类型 既然它的位置不相符那么我们可以这样写吗 可以是可以但是流插入的本质是:应该是对象流入到console里面去,而不是console流入到对象里 对于流提取同理 这时候我们把位于Date.h里原本成员函数的声明注释掉 我们在全局定义一个重载的函数定义成全局的声明,此时经过编译后又引发了一个新问题 面对这样的情况该如何去纠正 在类的外部要想访问内部私有成员用友元声明:在类的公有和私有声明都可以 我们发现就可以编译通过了 并且类型的顺序也是匹配的: 但是当咱们连续输出两个自定义对象的时候编译就不会通过了看下面解析 这时候我们把.h里面的友元的返回值改成ostream、全局声明和.cpp里面的返回值也改成一样 对于流提取,并不能给声明加const: 总结 内置类型可以使用是因为函数重载加运算符重载 自定义类型使用的方式是重载这个流插入和流提取的运算符 问题:现在尝试去重载operator然后发现没办法将operator重载成成员函数。 因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象才能正常使用。 class Date
{
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}
// d1 cout; - d1.operator(d1, cout); 不符合常规调用
// 因为成员函数第一个参数一定是隐藏的this所以d1必须放在的左侧ostream operator(ostream _cout){_cout _year - _month - _day endl;return _cout;}
private:int _year;int _month;int _day;
}; 所以要将operator重载成全局函数。但又会导致类外没办法访问成员此时就需要友元来解决。operator同理。 友元函数可以直接访问类的私有成员它是定义在类外部的普通函数不属于任何类但需要在 类的内部声明声明时需要加friend关键字。 友元类 友元类的所有成员函数都可以是另一个类的友元函数都可以访问另一个类中的非公有成员 友元关系是单向的不具有交换性。 比如下面的Time类和Date类 在Time类中声明Date类为其友元类 那么可以在Date类中直接 访问Time类的私有成员变量 但想在Time类中访问Date类中私有的成员变量则不行 。 友元关系不能传递 如果C是B的友元 B是A的友元则不能说明C时A的友元。 友元关系不能继承在继承位置再给大家详细介绍 class Time
{friend class Date; // 声明日期类为时间类的友元类//则在日期类中就直接访问Time类中的私有成员变量
public:Time(int hour 0, int minute 0, int second 0): _hour(hour), _minute(minute), _second(second){}private:int _hour;int _minute;int _second;
};
class Date
{
public:Date(int year 1900, int month 1, int day 1): _year(year), _month(month), _day(day){}void SetTimeOfDate(int hour, int minute, int second){// 直接访问时间类私有的成员变量_t._hour hour;_t._minute minute;_t._second second;}private:int _year;int _month;int _day;Time _t;
}; 内部类 内部类概念 概念如果一个类定义在另一个类的内部这个内部类就叫做内部类。内部类是一个独立的类 它不属于外部类更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越 的访问权限 注意内部类就是外部类的友元类 参见友元类的定义内部类可以通过外部类的对象参数来访 问外部类中的所有成员。但是外部类不是内部类的友元。 从下面这张图看出来什么 总结 1.B类 受A类域和访问限定符的限制其实它们是两个独立的类 2.内部类默认就是外部类的友元 -- 内部类可以访问外部类外部类不能访问类部类 特性 1. 内部类可以定义在外部类的 public 、 protected 、 private 都是可以的。 2. 注意内部类可以直接访问外部类中的 static 成员不需要外部类的对象 / 类名。 3. sizeof( 外部类 ) 外部类和内部类没有任何关系。 代码示例 class A
{
private:static int k;int h;
public:class B // B天生就是A的友元{public:void foo(const A a){cout k endl;//OKcout a.h endl;//OK}};
};
int A::k 1;
int main()
{A::B b;b.foo(A());return 0;
} 执行 优化面试题
求123...n_牛客题霸_牛客网 (nowcoder.com)
class Solution {class Sum {public:Sum(){_ret _i;_i;}};public:int Sum_Solution(int n) {Sum a[n];return _ret;}private:static int _i;static int _ret;
};
int Solution::_i 1;
int Solution::_ret 0; 匿名对象
#includeiostream
using namespace std;
class A
{
public:A(int a 0):_a(a){cout A(int a) endl;}~A(){cout ~A() endl;}
private:int _a;
};
class Solution {
public:int Sum_Solution(int n) {//...return n;}
};
int main()
{A aa1;// 不能这么定义对象因为编译器无法识别下面是一个函数声明还是对象定义//A aa1();// 但是我们可以这么定义匿名对象匿名对象的特点不用取名字// 但是他的生命周期只有这一行我们可以看到下一行他就会自动调用析构函数A();A aa2(2);// 匿名对象在这样场景下就很好用当然还有一些其他使用场景这个我们以后遇到了再说Solution().Sum_Solution(10);return 0;
}
执行 匿名对象和有名对象 例子 拷贝对象时的一些编译器优化
知识点回顾 C默认兼容C语言默认生成的拷贝构造函数对内置类型会完成值拷贝跟结构体拷贝一样但是又规定自定义类型传值传参过程中符合拷贝构造:拿一个以及存在的对象去初始化另一个对象 为什么要规定调拷贝构造?因为直接搞值拷贝会有很大的问题比如说像栈、顺序表、链表这样的类(析构两次)对象里面可能还有一个指针指向一块空间这时候就要完成深拷贝那想让这个拷贝正确该怎么办呢就要自己去写那个深拷贝 所以c在这一块完成了完美的兼容对于日期类就算要浅拷贝(不写编译器默认生成的拷贝构造)编译器有着个性化处理对于栈要自己写深拷贝对于日期类写不写都行 示例(包含讲解)
比如下面这个例子中传值传参引发了对象的拷贝拷贝要调用拷贝构造拷贝出aa1的副本aa然后出了作用域aa先析构回到main函数之后出了作用域aa1再调析构 可以想象一下假设我仅仅只想调用一下Print()有没有必要使用拷贝构造没有吧。 我们要给这个形参加上引用同时加上const这样的话就不会引发拷贝也保护了对象不可修改 同时插播一下这两者是有着显著区别的 其实对于void f1的(A aa)这个地方可以不加const这属于权限的缩小但是对于匿名对象来说可不行 因为f1(A())这一行试图将一个匿名临时对象传递给需要非const引用参数的函数f1(A aa)。 匿名临时对象不能绑定到非const引用上因为匿名临时对象生命周期结束后会自动销毁 而非const引用可能会尝试修改临时对象这是不允许的。 所以要给这个函数加上 const--void f1(const A aa) 另外 我们知道匿名对象的生命周期只在这一行但是const引用会延迟匿名对象的生命周期 传值传参和传值返回
对于编译器处理 传值传参和传值返回的总结 传值返回 -- 不能带引用返回因为aa出了这个作用域调析构了。 如果返回了aa的引用意味着返回的引用指向了一个已经销毁的对象在实际运行时可能导致各种难以预料的问题 class A
{
public:A(int a 0):_a(a){cout A(int a) endl;}A(const A aa):_a(aa._a){cout A(const A aa) endl;}A operator(const A aa){cout A operator(const A aa) endl;if (this ! aa){_a aa._a;}return *this;}~A(){cout ~A() endl;}
private:int _a;
};
void f1(A aa)
{}
A f2()
{A aa;return aa;
}
int main()
{// 传值传参A aa1;f1(aa1);cout endl;// 传值返回f2();cout endl;return 0;
} 对于析构的分析 对于f2()仅此于f2(),我们分析一下析构 第一次析构在 f2 函数内部局部变量 aa 在 return aa; 语句处会触发一次析构。这是因为 aa 是 f2 函数的局部对象当函数执行完毕时局部对象的生命周期结束因此会调用析构函数。 第二次析构f2 函数返回的是 A 类的一个对象但由于它是通过值返回的所以在 f2() 调用处会创建一个临时对象接收返回值。然而由于这个临时对象在表达式结束之后没有被存储到任何地方因此它也会在表达式也就是f2() 结束时立即被销毁从而触发第二次析构。 构造拷贝构造
一个表达式连续的步骤里面连续的构造会被合并 f1(1)隐式类型连续构造(构造函数)拷贝构造-优化为直接构造 f1(A(2))一个表达式中 连续构造(构造函数)拷贝构造-优化为一个构造 连续的拷贝构造 一个表达式中连续拷贝构造拷贝构造-优化一个拷贝构造 拷贝构造赋值重载(无法优化) 一个表达式中连续拷贝构造赋值重载-无法优化 //上文有A类的定义
A f2()
{A aa;return aa;
}
int main()
{// 一个表达式中连续拷贝构造赋值重载-无法优化A aa1;aa1 f2();cout endl;return 0;
} 执行 拷贝构造的aa返回的临时拷贝也就是回到main函数之后的那个临时对象(黄色字)要等到赋值运算符重载完毕之后,才调的析构 再次理解类和对象 类和对象篇就此结束接下来是内存管理。 本文修改次数0 更新时间2024年3月1日