坑梓网站建设价格,南京网站建设 小程序,商标 做网站 是几类,网站seo哪家公司好目录
1.再谈构造函数
1.1 构造函数体赋值
1.2 初始化列表
1.3 explicit关键字
2.STATIC成员
2.1 概念
2.2 特性
3.C中成员初始化的新玩法
4.友元
4.1 友元函数
4.2 友元类
5.内部类
6.再次理解封装
7.再次理解面向对象 本次内容大纲#xff1a; 1.再谈构造函数 …目录
1.再谈构造函数
1.1 构造函数体赋值
1.2 初始化列表
1.3 explicit关键字
2.STATIC成员
2.1 概念
2.2 特性
3.C中成员初始化的新玩法
4.友元
4.1 友元函数
4.2 友元类
5.内部类
6.再次理解封装
7.再次理解面向对象 本次内容大纲 1.再谈构造函数
1.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 year, int month, int day){_year year; //第一次赋值_year 2024; //第二次赋值//.....多次赋值_month month;_day day;}private:int _year;int _month;int _day;};
在函数体中我们可以多次给成员对象赋值而不是初始化
1.2 初始化列表
初始化列表以一个冒号开始接着是一个以逗号分隔的数据成员列表每个成员变量后面跟一个放在括号中的初始值或表达式。
class Date
{
public:Date(int year, int month, int day)//初始化列表:_year(year),_month(month),_day(day){}
private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 9, 15);return 0;
}
注意
1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员必须放在初始化列表位置进行初始化
引用成员变量const成员变量自定义类型成员(且该类没有默认构造函数时)
引用成员变量和const成员变量都要一个相同的特点在定义时必须初始化所以它们只能使用初始化列表初始化
引用创建时必须初始化
int a; //errorint b 10;
int a b; //创建就初始化
const变量创建时必须初始化
const int a; //error 创建时未初始化const int a 10; //创建时初始化
自定义成员
若类中没有默认构造函数我们实例化该类对象时应该传参对其初始化即初始化没有默认构造函数的类对象时必须使用初始化列表来对其进行初始化
在这里说明一下默认构造函数是指不用传参就可以调用的构造函数
1.我们不写构造函数编译器自己生成的
2.无参的构造函数
3.全缺省的构造函数
typedef int DateType;
class Stack
{
public://构造函数Stack(int capacity){_a (DataType*)malloc(sizeof(DataType) * capacity);if(nullptr _a){perror(Stack::malloc);exit(-1);}_top 0;_capacity capacity;}//析构函数~Stack(){if (_a){free(_a);_a nullptr;_top _capacity 0;}}
private:int* _a;int _top;int _capacity;
};class A
{
public:A(int ref, int a):_ref(ref),_a(a),_st(10){}
private:int _ref; //引用const int _a 2; //constStack _st; //没有默认构造函数
};int main()
{int p 10;A a(p, 20);return 0;
}
在定义时就得初始化得变量类型就必须放在初始化列表进行初始化
还有一个要注意得是可以看到在构造函数的参数中其中ref引用的意义是防止野引用
如果这里是int ref的话将一个局部变量给_ref初始化就会导致_ref引用到一个被释放的空间
在类中声明时给值const int _a 2;这个意思是给初始化列表缺省值如果我们没有在初始化列表对_a进操作_a就会被赋值为2
3. 尽量使用初始化列表初始化
当你实例化一个对象时初始化列表就是对象成员定义的地方无论你是否使用初始化列表每个对象成员都会走一遍初始化列表成员变量需要定义出来
对于内置类型使用初始化列表和在构造函数体内初始化实际上没有什么区别其差别就类似以下代码
//使用初始化列表初始化
int a 10;//使用函数体初始化(没有使用初始化列表)
int a;
a 10;对于自定义类型使用初始化列表还可以提高代码效率
class Time
{
public:Time(int hour 1){_hour hour;}
private:int _hour;
};class test
{
public:test():_t(12) //调用拷贝构造函数{}
private:Time _t;
};
在类test中我们调用了一次类Time中的构造函数
在不使用初始化列表的情况下
class Time
{
public:Time(int hour 1){_hour hour;}
private:int _hour;
};class test
{
public:test(int hour){//初始化列表调用了一次构造函数不使用初始化列表也会走这个流程Time tmp(hour); //调用一次构造函数_t tmp; //调用赋值运算符重载函数}
private:Time _t;
};
与上面那段代码对比这里多调用一次构造函数和赋值运算符重载函数
初始化野不能解决我们百分之百的初始化问题
例如
情况一
typedef int DataType;class Stack
{
public://构造函数Stack(int capacity):_a((DataType*)malloc(sizeof(DataType)* capacity)), _top(0),_capacity(capacity){//我们需要_a做检查初始化列表完成不了if(nullptr _a){perror(Stack::malloc);exit(-1);}//我们还需要_a数组初始化一下初始化初始化列表也完成不了memset(_a, 0, sizeof(int) * _capacity);}//析构函数~Stack(){if (_a){free(_a);_a nullptr;_top _capacity 0;}}
private:DataType* _a;int _top;int _capacity;
};
情况二
使用构造函数申请一个二维数组
class AA
{
public:AA(int row, int col):_row(row),_col(col){_a (int**)malloc(sizeof(int*) * row);for (int i 0; i col; i){_a[i] (int*)malloc(sizeof(int) * col);}}
private:int** _a;int _row;int _col;
};
初始化列表并不能帮我们完成初始化所有的事情
4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序与其在初始化列表中的先后次序无关
class A
{
public:A(int a):_a1(a), _a2(_a1){}void Print() {cout _a1 _a2 endl;}
private:int _a2;int _a1;
};//A.输出1 1
//B.程序崩溃
//C.编译不通过
//D.输出1 随机值
测试运行 答案D
1.3 explicit关键字
构造函数不仅可以初始化和赋值对于单个参数的拷贝构造函数还支持隐式类型转换
class A
{
public:A(int a):_a(a){cout A(int a) endl;}A(const A aa):_a(aa._a){cout A(const A aa) endl;}
private:int _a;
};int main()
{A a(1);A b 2; //隐式类型转换从内置类型到自定义类型return 0;
}
对于单个参数的拷贝构造函数编译器是允许这样调用构造函数的实际上这里还调用了一次拷贝构造函数
在语法上这里一段代码A b 2等价于下面两句代码
A tmp(2); //调用构造函数
A b tmp; //调用拷贝构造函数
先构造在拷贝构造
早期的编译器就是这么处理的当编译器遇到A b 2;时它会先调用构造函数创建一个临时对象然后讲这个临时对象拷贝构造给b。但是现在编译器已经做了优化遇到A b 2;时会按照A b(2);来进行处理只会调用一次构造函数这就是隐式类型转换
在我们仪以前的学习中其实我们也遇到过隐式类型转换例如
int a 10;
double b a;
在这个过程中为了保护a的值不被破环会产生一个临时变量来存放a的值然后将这个临时变量赋值给b 从汇编可以看到确实是这样操作的
但是A b 2这种形式的可读性不是很好所有我们可以使用explicit关键字来修饰构造函数这样就可以禁止单参数构造函数的隐式类型转换 可以看到加入explicit之后就报错了
2.STATIC成员
2.1 概念
声明为static的类成员称为类的静态成员用static修饰的成员变量称之为静态成员变量用static修饰的 成员函数称之为静态成员函数。静态成员变量一定要在类外进行初始化
2.2 特性
1. 静态成员为所有类对象所共享不属于某个具体的对象存放在静态区
例: 可以看到类中的静态变量不计入类大小因为它存在于静态区但是它属于类属于这个类所有对象的成员
2. 静态成员变量必须在类外定义定义时不添加static关键字类中只是声明
class A
{
private:static int _a;
};int A::_a 10;
单独一个类对象成员定义的地方是初始化列表但是_a是静态成员变量属于所有A类对象的成员不是属于某一个对象的所有它不能使用初始化列表也不能给初始值所以它是一个特例不受访问限定符的影响在全局域初始化
3. 静态成员函数没有隐藏的this指针不能访问任何非静态成员
例
class A
{
public:A(int p):_p(p){cout A() endl;}static void Print(){cout _p _a endl;}
private:int _p;static int _a;
};int A::_a 10;int main()
{A a(5);a.Print();return 0;
} 静态成员函数里面不能使用非静态成员
tips
含有静态成员变量的类一般含有一个静态成员函数用于访问静态成员变量
4. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
1当静态成员变量公有时有以下几种访问形式
class A
{
public:int _p;static int _a;
};int A::_a 10;int main()
{A a;cout a._a endl; //使用类对象突破类域对静态成员进行访问cout A()._a endl; //使用匿名结构体突破类域对静态成员进行访问cout A::_a endl; //使用类名突破类域对静态成员进行访问
}
2当静态成员变量是私有时有以下几种访问形式
class A
{
public:int _p;//没有this指针指定类域和访问限定符就可以访问static int GetACount(){return _a;}
private:static int _a;
};int A::_a 10;int main()
{A a;cout a.GetACount() endl; //通过对象调用静态成员函数来进行访问cout A().GetACount() endl; //通过匿名对象调用静态成员函数来进行访问cout A::GetACount() endl; //通过类名调用静态成员函数进行访问return 0;
}
这里说明一下
使用非静态成员函数只能 得到_a的值来使用它的值并不能访问_a
class A
{
public:int _p;int GetACount(){return _a;}
private:static int _a;
};int A::_a 10;int main()
{A a;cout a.GetACount() endl; //通过对象调用静态成员函数来得到_a的值return 0;
}
5. 静态成员也是类的成员受public、protected、private 访问限定符的限制
当静态成员是private时尽管突破了类域也不能对其进行访问
【问题】
1. 静态成员函数可以调用非静态成员函数吗
不可以因为非静态成员函数第一个形参是this指针而静态成员函数中没有this指针所以静态函数成员不能调用非静态函数
2. 非静态成员函数可以调用类的静态成员函数吗
可以因为在类域中静态成员函数不受类域和访问限定符的限制
使用static情景
设计一个类只能在栈上创建对象
class A
{
public:static A GetStackObj(){A N1;return N1;}
private:A(){}
private:int _a;int _b;
};int main()
{A::GetStackObj();return 0;
}
3.C中成员初始化的新玩法
C11支持非静态成员变量在声明时进行初始化但是要注意这里不是初始化而是给初始化列表一个缺省值
class A
{
public:A(){}private://非静态成员声明时可以给缺省值int* _a (int*)malloc(sizeof(int) * 10); int _b 10;static int _c; //非静态对象不能直接给缺省值
};
初始化列表是你初始化定义的地方如果你没有给值那它就会使用你给的缺省值如果你给了值那么它就不会使用你的缺省值
我们学习了静态成员变量现在我们来将他运用到题目中
例
这是牛客上的一道题目大家可以思考一下使用静态变量怎么将这道题目写出来 class sum
{
public:sum(){_ret _i;_i;}static int GetACount(){return _ret;}
private:static int _ret;static int _i;
};int sum::_ret 0;
int sum::_i 1;class Solution
{
public:int Sum_Solution(int n) {sum a[n];return sum::_ret;}
};
4.友元
友元提供了一种突破封装的方式有时提供了便利。但是友元会增加耦合度就是双方之间的亲密度破坏了封装所以友元不宜多用。
友元分为友元函数和友元类
4.1 友元函数
问题现在尝试去重载operator然后发现没办法将operator重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象才能正常使用。所以要将operator重载成成员函数但又会导致类外没办法访问成员此时就需要友元来解决。operator同理。
我们知道在C有一件很神奇的事就是cout和cin可以自动识别输入和输出对象的类型我们使用它们时不用像C语言一样增加输入输出的格式这给我们提供了便利难道C真有这么神奇吗其实不是内置类型的变量可以直接使用cout和cin是因为它们在库中已经实现了内置类型于运算符的重载 可以查看到cout在ostream这个类中而cin在istream这个类中
我们来实现一下自定义运算符的重载让自定义类型也可以使用和
这里友元函数就起到作用了
class Date
{friend ostream operator(ostream _cout, const Date d);friend istream operator (istream _cin, Date d);
public:Date(int year 1, int month 1, int day 1):_year(year),_month(month),_day(day){}private:int _year;int _month;int _day;
};ostream operator(ostream _cout, const Date d)
{_cout d._year d._month d._day;return _cout;
}istream operator(istream _cin, Date d)
{_cin d._year d._month d._day;return _cin;
}int main()
{Date d1;cin d1;cout d1;return 0;
}
这里上一节更详细大家有兴趣的可以去看看这里是链接 点击这里
注
cout是ostream类中的一个全局对象cin是istream类中的一个全局对象和重载具有返回值是为了连续的实现和的连续运算它们的生命周期都是整个工程 说明
友元函数可访问类的私有和保护成员但不是类的成员函数
友元函数不能用const修饰
友元函数可以在类定义的任何地方声明不受类访问限定符限制
一个函数可以是多个类的友元函数
友元函数的调用与普通函数的调用原理相同 4.2 友元类
声明友元类之后Date中你所有成员函数都是Time中的友元函数即Date中的所有成员函数都可以访问Time中的私有成员
//友元类
class Time
{//声明Date是Time的友元类friend class Date;
public:Time(int hour 1, int minute 1, int second 1):_hour(hour),_minute(minute),_second(second){}
private:int _hour;int _minute;int _second;
};class Date
{
public:Date(int year 1, int month 1, int day 1):_year(year),_month(month),_day(day){}//使用Time类void SetTimeOFdate(int hour 1, int minute 1, int second 1){T._hour hour;T._minute minute;T._second second;}private:int _year;int _month;int _day;Time T;
};友元类的所有成员函数都可以是另一个类的友元函数都可以访问另一个类中的非公有成员。
友元关系是单向的不具有交换性。比如上述Time类和Date类在Time类中声明Date类为其友元类那么可以在Date类中直接访问Time 类的私有成员变量但想在Time类中访问Date类中私有的成员变量则不行。友元关系不能传递 如果B是A的友元C是B的友元则不能说明C时A的友元。友元关系不能继承在继承位置再给大家详细介绍。 5.内部类
概念如果一个类定义在另一个类的内部这个内部类就叫做内部类。内部类是一个独立的类它不属于外部类更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意内部类就是外部类的友元类参见友元类的定义内部类可以通过外部类的对象参数来访问外部类中 的所有成员。但是外部类不是内部类的友元。
特性
1. 内部类可以定义在外部类的public、protected、private都是可以的。
2. 注意内部类可以直接访问外部类中的static成员不需要外部类的对象/类名。
3. sizeof(外部类)外部类和内部类没有任何关系。
class A
{
private:static int _a;int _b;//B是A的内部类B类是A类的天生友元class B{private:void foo(A a){cout a._b endl;cout _a endl;}};
};
计算大小为4 这里A的大小为4内部类不算做整体的大小
学习了内部类那么这道题目就可以使用内部类来完成 class Solution {class sum {public:sum() {_ret _i;_i;}};public:int Sum_Solution(int n) {sum a[n];return _ret;}static int _ret;static int _i;
};int Solution::_ret 0;
int Solution::_i 1;
6.再次理解封装
C是一门面向对象的语言面向对象编程语言具有三个特征封装继承多态
C通过类将数据的属性和操作数据的方法封装在一起进行结合就像去旅游一样去参观一个景点需要先预约买票然后排队进入进去之后只供观赏。若兵马俑可以让人随意的触摸你一下我一下这些文物能保留多久呢
所以我们去参观兵马俑时他只允许我们观赏和拍照并不允许我们做其他操作这就是属于封装通过将景点封装起来提供一个行为供我们使用。防止物品不让其他人破坏
博物馆管理系统
售票系统负责将票卖给游客
工作人员: 检票打扫服务安保卫生等
导游带领客户到指定位置参观博物馆并且给客户讲解物品的缘由历史等 通过对比理解其实C也是一样将对象的属性和行为封装在一个类中通过访问限定符对类中的内容来进行管理将用户需要的我们能提供的设置为public为用户提供一个接口至于其中实现的原理用户就不需要知道知道反而会增加使用或者维护的难度
7.再次理解面向对象
其实面向对象就是模拟抽象现实世界