类与对象;

目录

一、认识类;

1、类的引入;

2、类的定义;

类的两种定义方式:

3、类的访问限定符及封装;

4、类的作用域;

5、类的实例化;

6、类对象模型;

计算类对象的大小;

代码:

运行结果:

结果分析:

类对象的存储方式;

结构体内存对齐规则;

7、this指针;

代码:

运行结果:

结果分析:

代码:

运行结果:

代码分析:

二、类的默认成员函数和成员;

1、构造函数;

定义:

特点:

2、析构函数;

概念:

特性:

代码:

运行结果:

结果分析:

3、拷贝构造函数;

概念:

特征:

代码:

运行结果:

4、赋值运算符重载;

概念:

代码(以date类举例):

运行结果:

​编辑

结果分析:

+=运算符和+运算符;

代码:

代码分析:

+=负数;

代码:

代码分析:

>>和<<运算符;

代码:

代码分析:

前置++和后置++运算符;

代码:

代码分析:

赋值运算符重载

赋值运算符重载格式

代码:

初始化列表

代码:

代码分析:

参数隐式类型转换;

单参数;

代码:

代码分析:

运行结果:

C++11支持多参数隐式类型转换;

代码:

代码分析:

运行结果:

explicit:

static;

概念:

特性:

代码:

代码分析:

运行结果:

友元;

友元函数:

说明:

代码:

代码分析:

友元类:

代码:

代码分析:

内部类;

概念:

特性:

代码:

代码分析:

运行结果:


一、认识类;

1、类的引入;

C 语言结构体中只能定义变量,在 C++ 中,结构体内不仅可以定义变量,也可以定义函数。 比如:之前在数据结构初阶中,用C 语言方式实现的栈,结构体中只能定义变量 ;现在以 C++ 方式实现, 会发现 struct中也可以定义函数。C++兼容C语言;结构可以继续使用;结构体升级成了类;
struct stack//还可以定义成员函数;
{void init()//在里面写可以省略很多参数;{a = nullptr;size = 0;capa = 0;}void push(int x){}int* a;int size;int capa;
};int main()
{stack s1;s1.init();s1.push(1);struct stack s2;s2.init();s2.push(2);return 0;
}

2、类的定义;

class定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面号不能省略。类体中内容称为类的成员:类中的变量称为类的属性成员变量; 类中的函数称为类的方法或者成员函数

class className
{类体:由成员函数和成员变量组成
};

类的两种定义方式:

声明和定义全部放在类体中,需注意:成员函数如果 在类中定义 ,编译器可能会将其当成
联函数 处理。
类声明放在 .h 文件中,成员函数定义放在 .cpp 文件中,注意: 成员函数名前需要加类名 ::;

3、类的访问限定符及封装;

public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访问(此处protectedprivate是类似的);访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止;如果后面没有访问限定符,作用域就到 } 即类结束。class的默认访问权限为privatestructpublic(因为struct要兼容C);类里面不受访问限定符的限制,访问限定符限制的是外面的人使用;

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来 和对象进行交互。

class stack//还可以定义成员函数;    //类里面不受访问限定符的限制,访问限定符限制的是外面的人使用;
{
public://类一开始默认为私有的;struct一开始默认为公有的;void init()//在里面写可以省略很多参数;{a = nullptr;size = 0;capa = 0;}void push(int x){if (size == capa){size_t newcapa = capa == 0 ? 4 : 2 * capa;a =(int*) realloc(a,newcapa * (sizeof(int)));if (a == nullptr){return;}capa = newcapa;}a[size++] = x;}int top()//栈顶元素;{return a[size - 1];}void destroy(){free(a);a = nullptr;size = capa = 0;}
private://在外面不能访问;访问限定符范围是到下一个访问限定符、int* a;int size;//int capa;
};
int main()
{stack a;a.init();a.push(1);a.push(2);a.push(3);a.push(4);/*cout << a.size << endl;cout << a.a[a.size-1] << endl;*///cout << a.top() << endl;return 0;
}

4、类的作用域;

类定义了一个新的作用域 ,类的所有成员都在类的作用域中 在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。类可以声明和定义分离;
class stack//还可以定义成员函数;    //类里面不受访问限定符的限制,访问限定符限制的是外面的人使用;
{//类是一个整体,没有什么向上向下找;
public://类一开始默认为私有的;struct一开始默认为公有的;void init();//在里面写可以省略很多参数;void push(int x);//函数在类里面直接定义,默认为内联;int top();//栈顶元素;void destroy();
private://在外面不能访问;访问限定符范围是到下一个访问限定符、int* a;int size;//int capa;
};
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:6031)
#include"stack.h"
void stack::init()//只有加了::后函数里面才能访问私有变量;默认去全局找,加了限定符就可以去找;
{a = nullptr;size = 0;capa = 0;
}
void stack::push(int x)
{if (size == capa){size_t newcapa = capa == 0 ? 4 : 2 * capa;a = (int*)realloc(a, newcapa * (sizeof(int)));if (a == nullptr){return;}capa = newcapa;}a[size++] = x;
}
int stack::top()//栈顶元素;
{return a[size - 1];
}
void stack::destroy()
{free(a);a = nullptr;size = capa = 0;
}

5、类的实例化;

用类类型创建对象的过程,称为类的实例化;声明,没有定义;声明和定义的区别:声明没有创造空间,定义创造空间;类存在文件系统里,不占内存的空间;(类似于建房的图纸);类实例化出来才是建的房子;类要定义一个具体变量后才能使用;函数类似于小区 的篮球场游泳馆等公共设施,存在公共代码区(全局函数也存在里面)(不在对象里);变量类似于每一栋小房子;

6、类对象模型;

计算类对象的大小;

代码:
class A1 {
public:void f1() {}
private:int _a;
};
class A2 {
public:void f2() {}
};
// 类中什么都没有---空类
class A3
{};
int main()
{cout << sizeof(A1) << endl;cout << sizeof(A2) << endl;cout << sizeof(A3) << endl;return 0;
}
运行结果:

结果分析:

函数类似于小区 的篮球场游泳馆等公共设施,存在公共代码区(全局函数也存在里面)(不在对象里);变量类似于每一栋小房子;所以类的大小就是变量存储的空间大小;有变量就按结构体内存对齐原则计算大小;没有变量大小就是1,内存是1是因为要分配一个字节占位,表示同类对象的区别,仅仅表示对象存在过(定义出来); 

类对象的存储方式;

一个类的大小,实际就是该类中 成员变量 之和,当然要注意内存对齐; 注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。
结构体内存对齐规则;
1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的对齐数为8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
问:为什么要内存对齐?
答:cpu去访问内存的时候,一般不是从任意位置访问,一般依次访问32位,例如四字节四字节访问;如果不内存对齐,访问一个数据可以要读多次,读多次会有多余变量,有可能涉及到数据拼接问题;可以通过program 来改变默认对齐数;

7、this指针;

C++ 编译器给每个 非静态的成员函数 增加了一个隐藏 的指针参数,让该指针指向当前对象 ( 函数运行时调用该函数的对象 ) ,在函数体中所有 成员变量 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编 译器自动完成
1. this 指针的类型:类类型 * const ,即成员函数中,不能给 this 指针赋值。
2. 只能在 成员函数 的内部使用。
3. this 指针本质上是 成员函数 的形参 ,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以 对象中不存储 this 指针
4. this 指针是 成员函数 第一个隐含的指针形参,一般情况由编译器通过 ecx 寄存器自动传
递,不需要用户传递。
代码:
class date
{        
public:void init(int year, int month, int day){                                      //this指针不可以在形参和实参显式的写,但是可以在类里面显示的用_year = year;//this->_year = year;_month = month;_day = day;}void print(){cout << _year << "/" << _month << "/" << _day << endl;}void printpos(){cout << this << endl;}
private://类只存变量;int _year;//加_是为了好区分;int _month;int _day;
};
int main()
{date d1;d1.init(1, 2, 3);//d1.init(&d1,1, 2, 3);cout << &d1 << endl;d1.printpos();return 0;
}
运行结果:

结果分析:

init函数实际上是void init(date* this,int year, int month, int day)编译器加上this指针数;this指针就是定义的类的地址;this指针不可以在实参和形参显式的写,但是可以显式调用;

代码:
class a
{
public:void print(){cout << "print()" << endl;}
private:int _a;
};
int main()
{/*a* p = nullptr;p->print();*///p对象成员函数的地址不存在对象里,而是存在代码区,所以直接那函数名字地址去代码区找,所以没有对p解引用;a* p = nullptr;(*p).print();//print依旧不在对象里,所以依旧不会对p解引用;  //只有光写p->a不会崩;p->a=1会崩;return 0;
}
运行结果:

代码分析:

p对象成员函数的地址不存在对象里,而是存在代码区,所以直接那函数名字地址去代码区找,所以没有对p解引用;是否在p解引用看访问的成员是否在p里面;

二、类的默认成员函数和成员;

1、构造函数;

定义:

构造函数是一个特殊的成员函数,名字与类名相同(date类构造函数 date(x,y)),创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次

特点:

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象

1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器 自动调用 对应的构造函数。
4. 构造函数可以重载。
	date(){cout << "date()" << endl;_year = 1;_month = 1;_day = 1;}//带参初始化;date(int year, int month, int day){cout << "date(int year, int month, int day)" << endl;_year = year;_month = month;_day = day;}上面两个构造合并;date(int year=1, int month=1, int day=1)//{cout << "date(int year=1, int month=1, int day=1)" << endl;_year = year;_month = month;_day = day;}
注意:如果通过无参构造函数创建对象时,对象后面不用跟括号(date d1),否则就成了函数声明(date d1());第一个和第三个语法上可以同时存在 ,但是使用的时候不可以;
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦
用户显式定义编译器将不再生成。
默认构造的特点:
1、我们写它生成,我们写了它不生成;
2、内置类型:语言原生的类型(指针都是内置类型);自定义:类、结构;C++默认构造对内置类型不处理、处理自定义类型(C++支持声明给缺省值),会去调用这个成员的默认构造函数;
什么是默认构造:
无参的构造函数、全缺省的构造函数、我们不写编译器自动生成的构造函数都被称为默认构造函数(不传参就会调用),他们只能有一个;

2、析构函数;

概念:

通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作

特性:

析构函数 是特殊的成员函数,其 特征 如下:
1. 析构函数名是在类名前加上字符 ~
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载。
4. 对象生命周期结束时, C++ 编译系统系统自动调用析构函数。(例如date类的析构函数是~date())。
5、如果系统没有显示定义,会生成默认析构,对于内置类型不处理,自定义类型调用它的析构函数处理。
代码:
class x
{
public:x(int a=1){cout << "x()" << endl;_a = a;}~x(){cout << "~x()" << endl;}
private:int _a;
};
int main()
{x x1;return 0;
}

运行结果:

结果分析:

构造和析构的最大特性是自动调用。

3、拷贝构造函数;

概念:

拷贝构造函数只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存 在的类类型对象创建新对象时由编译器自动调用

特征:

拷贝构造函数也是特殊的成员函数,其 特征 如下:
1. 拷贝构造函数 是构造函数的一个重载形式
2. 拷贝构造函数的 参数只有一个 必须是类类型对象的引用 ,使用 传值方式编译器直接报错 , 因为会引发无穷递归调用。(如果是date(date d),那么d1(d2)会先d(d2),形成了新的拷贝构造,导致又要  d3(d2),无穷调用)。
若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝,会造成资源二次释放的问题。
代码:
class stack//还可以定义成员函数;    //类里面不受访问限定符的限制,访问限定符限制的是外面的人使用;
{
public://类一开始默认为私有的;struct一开始默认为公有的;stack(int n = 4){cout << "stack()" << endl;cout << "stack(int n = 4)" << endl;if (n == 0){a = (int*)malloc(sizeof(int) * 4);if (a == nullptr){perror("malloc fail");}size = 0;capa = 4;}else{a = (int*)malloc(sizeof(int) * n);if (a == nullptr){perror("malloc fail");}size = 0;capa = n;}}void push(int x){if (size == capa){size_t newcapa = capa == 0 ? 4 : 2 * capa;auto x1 = a;a = (int*)realloc(a, newcapa * (sizeof(int)));if (a == nullptr){perror("realloc fail");exit(-1);}capa = newcapa;}a[size++] = x;}void pop(){assert(size > 0);size--;}int top()//栈顶元素;{return a[size - 1];}void print(){cout << size << endl << capa << endl;}stack(const stack& s)//解决了两次析构问题;(深拷贝);{      //s不能改变;防止错误代码;cout << "stack(stack& s)" << endl;a= (int*)malloc(s.capa * (sizeof(int)));if (a == nullptr){perror("realloc fail");exit(-1);}memcpy(a, s.a,sizeof(int)*s.size);size = s.size;capa = s.capa;}~stack(){cout << "~stack()" << endl;free(a);a=nullptr;//这行和下一行不写不算错,算是好习惯;size = capa = 0;}bool isempty(){//assert(size>0);//return size == 0;}
private:int* a;int size ;int capa ;
};
class date
{
public:date(int year=2024, int month=11, int day=11){year_ = year;month_ = month;day_ = day;}void print(){cout << year_<<"/" << month_ << "/" << day_ << endl;}//	date d2(d1);  d是d1的别名,this是d2的指针;date(const date& d)//如果是date(date d),那么d1(d2)会先d(d2),形成了新的拷贝构造,导致又要  d3(d2),无穷调用;{    //防止错误的改变,防止误伤;cout << "date(date& d)" << endl;year_ = d.year_;month_ = d.month_;day_ = d.day_;}~date(){cout << "~date()" << endl;}
private:int year_;int month_;int day_;
};
void func1(date d)
{d.print();
}
void func2(stack& s)//s的作用域是main函数栈帧,但是问题是s变化s1也变化;
{s.push(1);s.push(2);
}
int main()
{date d1;func1(d1);stack s1;func2(s1);//会出现浅拷贝问题stack s2(s1);//以下两种写法一样;date d2(d1);date d3 = d1;return 0;
}
运行结果:

4、赋值运算符重载;

概念:

C++为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数,也具有其

返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字 operator 后面接需要重载的运算符符号
函数原型: 返回值类型  operator 操作符 ( 参数列表 )
注意:
不能通过连接其他符号来创建新的操作符:比如 operator@
重载操作符必须有一个类类型参数
用于内置类型的运算符,其含义不能改变,例如:内置的整型 + ,不 能改变其含义
作为类成员函数重载时,其形参看起来比操作数数目少 1 ,因为成员函数的第一个参数为隐
藏的 this
.* :: sizeof ?: . 注意以上 5 个运算符不能重载。这个经常在笔试选择题中出现。

代码(以date类举例):

class date
{
public:date(int year=2024, int month=11, int day=11){_year = year;_month = month;_day = day;}void print(){cout << _year<<"/" << _month << "/" << _day << endl;}date(const date& d);{   cout << "date(date& d)" << endl;_year = d._year;_month = d._month;_day = d._day;}bool operator<( const date& x2)//运算符重载可以写为全局函数,也可以写为类里面的成员函数;{if (_year < x2._year){return true;}else if (_year == x2._year && _month < x2._month){return true;}else if (_year == x2._year && _month == x2._month && _day < x2._day){return true;}else{return false;}}bool operator==(const date& x2){return _year == x2._year && _month == x2._month && _day == x2._day;}bool operator<=(const date& x2){return ((*this)<x2)|| ((*this) == x2);}bool operator>(const date& x2){return !((*this) <= x2);}bool operator!=(const date& x2){return !((*this) == x2);}~date(){cout << "~date()" << endl;}private:int _year;int _month;int _day;
};
int main()
{date d1(2023, 11, 11);date d2(2024, 12, 12);//为了对自定义类型进行比较,发明了运算符重载;int i1 = 10;int j1 = 100;i1 < j1;//内置类型转为运算符指令;cout << (d1< d2) << endl;//自定义类型转为运算符重载;//cout << (operator<(d2, d1) )<< endl;//可以显示调用;cout << (d1 < d2) << endl;//上面的自动转为下列公式;cout << d1.operator<(d2) << endl;cout << (d2 < d1) << endl;cout << d2.operator<(d1) << endl;cout << (d1 == d2)<<endl;cout << (d1 != d2) << endl;cout << (d1 <= d2) << endl;cout << (d1 > d2) << endl;return 0;
}
运行结果:

结果分析:

自定义类型原来不支持比较,写了运算符重载才支持;运算符重载不能改变操作符的操作数个数;写完运算符重载后可以显式调用也可以隐式调用。注意比较的时候操作数的左右顺序要和重载函数的顺序一致。

+=运算符和+运算符;

+=原来的数改变;+原来的数不改变;
代码:
	int getmonthday(int year,int month)//得到每年每月的天数。{int monthday[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if (month == 2&&((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))){//先判断是不是2月;monthday[2]+=1;}return monthday[month];}	
date& operator+=(int day){_day += day;while (_day > getmonthday(_year, _month))//多次循环,直到天数小于该月的天数;{_day -= getmonthday(_year, _month);_month++;if (_month > 12){_year++;_month = 1;}}return *this;}//+=的时候返回的是*this,出了作用域还在,可以用引用返回;+返回的是d2,出了作用域就不在了,使用传值返回;date operator+(int day){date d2(*this);//不写这个是+=//d2._day += day;//while (d2._day > getmonthday(d2._year, d2._month))//多次循环,直到天数小于该月的天数;//{//	d2._day -= getmonthday(d2._year,d2._month);//	d2._month++;//	if (d2._month > 12)//	{//		d2._year++;//		d2._month = 1;//	}//}//return d2;d2 += day;return d2;}
代码分析:

+可以通过拷贝构造,然后复用+=;+=的时候返回的是*this,出了作用域还在,可以用引用返回;+返回的是d2,出了作用域就不在了,使用传值返回;

+=负数;

代码:
date& date::operator-=(int day)
{if (day < 0){return *this += (-day);}_day -= day;while (_day < 1){_month--;if (_month < 1){_year--;_month = 12;}_day += getmonthday(_year, _month); }return *this;
}
date& date::operator+=(int day)
{if (day < 0){return *this -= (-day);}_day += day;while (_day > getmonthday(_year, _month)){_day -= getmonthday(_year, _month);_month++;if (_month > 12){_year++;_month = 1; }}  return *this;
}
代码分析:

+=负值等于-=正值;

>>和<<运算符;

代码:
ostream& operator<<(ostream& out, date& d)//<< 可以更好的打印自定义类型;
{out << d._year << "/" << d._month << "/" << d._day << endl;return out;//为了多个一块打印;
}
istream& operator>>(istream& in, date& d)
{in >> d._year >> d._month >> d._day; return in;
}
代码分析:

由于cout<<d;打印的顺序d在后,所以不能将<<重载函数写在类里面;只能写在类外;由于cout<<d1<<d2,打印完d1之后还要打印d2,所以返回值是out。

前置++和后置++运算符;

代码:
date& date::operator++()
{*this += 1;return *this;
}
date date::date::operator++(int)//int 只是为了匹配;
{date d(*this);*this += 1;return d;
}
代码分析:

后置++和前置++最后都是加1,但是返回值不同;

赋值运算符重载

赋值运算符重载格式
参数类型 const T& ,传递引用可以提高传参效率
返回值类型 T& ,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回 *this :要复合连续赋值的含义
赋值重载如果没有显示定义;默认赋值重载函数:内置类型值拷贝,自定义类型调用赋值重载函数;
代码:
Date& operator=(const Date& d){if(this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}

初始化列表

初始化列表:以一个 冒号开始 ,接着是一个以 逗号分隔的数据成员列表 ,每个 " 成员变量 " 后面跟一个 放在括 号中的初始值或表达式。

代码:

class date
{
public:date(int year, int month, int day, int x, int& rex): _year(year)                                  , _month(month), _rex(rex), _x(x)//这就是定义的地方,在此位置初始化;//如果给了缺省值,这行不写也行(这行写的原因是给值,如果有缺省,可以不用写), a1(1)//没有默认构造,在定义的地方初始化;{//括号上面是定义的地方(初始化列表); //先走上面,再走函数体;所有成员都在上面定义//_x = x;//const这个位置不行_day = day;//普通成员可以在里面定义(因为普通成员不需要在定义的地方初始化);}void func1(){++_rex;}
private://支持给缺省值;缺省值给初始化列表;如果初始化列表没有显示给值,就用缺省值;显式给值,不用缺省值;int _year;//每个成员的声明(不开空间)int _month;int _day;//成员必须在定义的时候初始化const int _x;//const 成员必须在定义的时候初始化,以后不能修改;int& _rex;//引用也必须在定义的时候初始化;A a1;
};
代码分析:
初始化列表;每个成员定义的地方;(开空间);(初始化列表是构造函数的一部分);初始化列表是成员定义的地方,不管写不写,都处理;初始化列表在没有写的一些变量,内置类型随机值,自定义类型调用默认构造;
初始化列表初始化的顺序是按声明的顺序;
1. 每个成员变量在初始化列表中 只能出现一次 ( 初始化只能初始化一次 )
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
引用成员变量;
const 成员变量;
自定义类型成员 ( 且该类没有默认构造函数时 );

参数隐式类型转换;

单参数;

代码:
class A
{
public:/*explicit*/ A(int a)//explicit 可以禁止单参数隐式类型转换:_a(a){cout << "A(int a)" << endl;}A(const A& a):_a(a._a){cout <<"A(const A& a)"<< endl;}~A(){cout << "~A" << endl;}
private:int _a;
};
int main()
{A a(1);A b = 2;const A& x= 2;return 0;
}
代码分析:
单参数隐式类型转换(传参可以直接传参数值,自动转为类);c++支持单参数隐式类型转换,用2调用a 构造函数创造临时对象,再用这个对象拷贝构造给b,后来创建对象时候,c++后来优化,不先构造对象,而是用2直接创造对象;x这里不能优化,用2调用a 构造函数创造临时对象,临时对象具有常性,权限不能放大,引用要加const;
运行结果:

C++11支持多参数隐式类型转换;

代码:
class B
{
public:/*explicit*/ B(int b1,int b2) //explicit 可以不允许隐式类型转换发生:_b1(b1),_b2(b2){cout << "B(int b1,int b2)" << endl;}B(const B& b):_b1(b._b1),_b2(b._b2){cout << "B(const B& b)" << endl;}
private:int _b1;int _b2;
};
int main()
{B b(1, 2);B b1 = { 1,2 }const B& c = { 1,2 };return 0;
}
代码分析:
与单参数类似,传参就能自动构造类,连续的构造拷贝构造可以省略;多参数隐式类型转换,用1,2调用b 构造函数创造临时对象,再用这个对象拷贝构造给b;引用的时候注意临时对象具有常性。
运行结果:

explicit:

explicit 修饰构造函数,将会禁止构造函数的隐式转换

static;

概念:

声明为 static 的类成员 称为 类的静态成员 ,用 static 修饰的 成员变量 ,称之为 静态成员变量 ;用 static 修饰 成员函数 ,称之为 静态成员函数 静态成员变量一定要在类外进行初始化。

特性:

1. 静态成员 所有类对象所共享 ,不属于某个具体的对象,存放在静态区。
2. 静态成员变量 必须在 类外定义 ,定义时不添加 static 关键字,类中只是声明。
3. 类静态成员即可用 类名 :: 静态成员 或者 对象 . 静态成员 来访问。
4. 静态成员函数 没有 隐藏的 this 指针 ,不能访问任何非静态成员。
5. 静态成员也是类的成员,受 public protected private 访问限定符的限制。
代码:
class A 
{
public:A(){n++;m++;}A(const A& a){int x = getm();n++;m++;}//必须要创建新的对象才能调用print函数;static int getm() //getm只能读不能改;{/*x++;*///静态成员函数不能访问非静态成员变量,因为没有this指针;return m;}static void print()//加了static,表示函数体内没有this指针;{cout << m << " " << n << endl;}~A(){m--;}
private://static int n = 0;//加了static就是全部类用的(存在静态区),属于所有对象,属于类域;//这里不能给缺省值;因为初始化列表是给某对象用的;static不是某个对象的变量,所有不走初始化列表;//static int m = 0;static int n;//static成员变量不是私有成员变量,所以不需要this指针,如果是公有的话,只需要突破类域;static int m;//在外面定义;int x;
};
int A::m = 0;
int A::n = 0;
int main()
{
A aa1;A aa2;A();A();//cout << "目前创建:" << A::n << " " << "目前使用:" << A::m << endl;//如果成员变量私有化,不能这样访问;A::print();//print()是static函数,没有this指针,可以通过类域,变量名,变量指针调用;aa1.print();A* x = nullptr;//因为调用函数不需要this指针,只需要突破类域;x->print();aa1.print();
}
代码分析:
m、n是static变量,要在外面定义;但是受访问限定符限制,所以类外面不能直接访问;print是static函数,因为静态成员函数没有this指针,且是公有,所以只需要突破类域就能访问;突破类域有三种方式:域访问限定符(::)、指针->、类变量。

运行结果:

友元;

友元函数:

友元函数可以直接访问类的私有成员,它是定义在类外部普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。有在类外面访问类成员的需求;(>>、<<重载函数);(定义私有成员的目的就是不被随便访问,所以最好不要定义太多友元函数);友元关系不具有传递性;(不到万不得已不要定义友元)。

说明:
友元函数 可访问类的私有和保护成员,但 不是类的成员函数。
友元函数 不能用 const 修饰。
友元函数 可以在类定义的任何。地方声明, 不受类访问限定符限制。
一个函数可以是多个类的友元函数。
友元函数的调用与普通函数的调用原理相同。
代码:
class Date
{friend ostream& operator<<(ostream& _cout, const Date& d);friend istream& operator>>(istream& _cin, Date& d);
public:Date(int year = 1900, 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;_cin >> d._month;_cin >> d._day;return _cin;
}
代码分析:
>>和<<是date的友元函数,所以可以访问私有成员变量。

友元类:

友元关系是单向的,不具有交换性。
类的私有成员变量,但想在 Time 类中访问 Date 类中私有的成员变量则不行。
友元关系不能传递
如果 B A 的友元, C B 的友元,则不能说明 C A 的友元。
代码:
class Time
{friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成,但是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;
};
int main()
{return 0;
}
代码分析:
上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time。反过来却不行。

内部类;

概念:

如果一个类定义在另一个类的内部,这个内部类就叫做内部类 。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:
内部类就是外部类的友元类 ,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

特性:

1. 内部类可以定义在外部类的 public protected private 都是可以的。
2. 注意内部类可以直接访问外部类中的 static 成员,不需要外部类的对象 / 类名。
3. sizeof( 外部类 )= 外部类,和内部类没有任何关系。
内部类类受外部类类域和访问限定符的限制,本身是两个独立的类。
代码:
class A
{
public:class B//{      //内部类是外部类的友元;内部类可以通过变量名+.访问私有成员变量;public:void func(){A a;a._a = 10;//内部类是外部类的友元;内部类可以通过变量名+.访问私有成员变量;}private:int _b;};
private:int _a;
};
int main()
{cout << sizeof(A) << endl;A aa1;A::B bb1;return 0;
}
代码分析:
B类受A类类域和访问限定符的限制,本身是两个独立的类;sizeof( 外部类 )= 外部类。

运行结果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/473331.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu22.04LTS 部署前后端分离项目

一、安装mysql8.0 1. 安装mysql8.0 # 更新安装包管理工具 sudo apt-get update # 安装 mysql数据库&#xff0c;过程中的选项选择 y sudo apt-get install mysql-server # 启动mysql命令如下 &#xff08;停止mysql的命令为&#xff1a;sudo service mysql stop&#xff0…

使用 Ant Design Vue 自定渲染函数customRender实现单元格合并功能rowSpan

使用 Ant Design Vue 自定渲染函数customRender实现单元格合并功能rowSpan 背景 在使用Ant Design Vue 开发数据表格时&#xff0c;我们常常会遇到需要合并单元格的需求。 比如&#xff0c;某些字段的值可能会在多行中重复出现&#xff0c;而我们希望将这些重复的单元格合并为…

27.<Spring博客系统③(实现用户退出登录接口+发布博客+删除/编辑博客)>

PS&#xff1a;关于打印日志 1.建议在关键节点打印日志。 ①请求入口。 ②结果响应 2.在可能发生错误的节点打印日志 3.日志不是越多越好。因为打日志也会消耗性能。 日志也可以配置去除重复日志。 一、用户退出功能 判断用户退出。我们只需要在前端将token删掉就可以了。 由于…

[前端面试]javascript

js数据类型 简单数据类型 null undefined string number boolean bigint 任意精度的大整数 symbol 创建唯一且不变的值&#xff0c;常用来表示对象属性的唯一标识 复杂数据类型 object&#xff0c;数组&#xff0c;函数,正则,日期等 区别 存储区别 简单数据类型因为其大小固定…

uniapp自动注册机制:easycom

传统 Vue 项目中&#xff0c;我们需要注册、导入组件之后才能使用组件。 uniapp 框架提供了一种组件自动注册机制&#xff0c;只要你在 components 文件夹下新建的组件满足 /components/组件名/组件名.vue 的命名规范&#xff0c;就能直接使用。 注意&#xff1a;组件的文件夹…

人工智能与SEO优化中的关键词策略解析

内容概要 在当今数字化快速发展的时代&#xff0c;人工智能&#xff08;AI&#xff09;与搜索引擎优化&#xff08;SEO&#xff09;的结合正变得愈发重要。关键词策略是SEO优化的一项基础工作&#xff0c;它直接影响到网站的可见性和流量。通过运用智能算法&#xff0c;企业能…

【异常解决】Linux shell报错:-bash: [: ==: 期待一元表达式 解决方法

博主介绍&#xff1a;✌全网粉丝21W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…

卷径计算(基于卷径变化微分方程计算实时卷径)

这里本质是积分法计算实时卷径,PLC里如何实现数值积分器算法请参考下面文章链接: 博途PLC数值积分器(矩形积分和梯形积分法自由切换) 博途PLC数值积分器(矩形梯形积分自由切换)_博图 积分计算-CSDN博客文章浏览阅读505次。本文详细介绍了博途PLC的数值积分器功能,涵盖了矩…

【Mysql】Mysql的多表查询---多表联合查询(上)

1、介绍 多表查询就是同时查询两个或者两个以上的表&#xff0c;因为有的时候&#xff0c;用户在查看数据的时候&#xff0c;需要显示的数据来自多张表&#xff0c;多表查询有以下分类&#xff1a; &#xff08;1&#xff09;交叉连接查询&#xff08;产生笛卡尔积&#xff0…

Shell基础(4)

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团…

跨平台WPF框架Avalonia教程 十五

ListBox 列表框 列表框从元素源集合中显示多行元素&#xff0c;并允许选择单个或多个。 列表中的元素可以组合、绑定和模板化。 列表的高度会扩展以适应所有元素&#xff0c;除非特别设置&#xff08;使用高度属性&#xff09;&#xff0c;或由容器控件设置&#xff0c;例如…

有序数组的平方(leetcode 977)

一个数组&#xff0c;返回一个所有元素的平方之后依然是一个有序数组。&#xff08;数组中含负数&#xff09; 解法一&#xff1a;暴力解法 所有元素平方后再使用快速排序法重新排序&#xff0c;时间复杂度为O(nlogn)。 class Solution { public:vector<int> sortedSqu…

使用 Go 实现将任何网页转化为 PDF

在许多应用场景中&#xff0c;可能需要将网页内容转化为 PDF 格式&#xff0c;比如保存网页内容、生成报告、或者创建网站截图。使用 Go 编程语言&#xff0c;结合一些现有的库&#xff0c;可以非常方便地实现这一功能。本文将带你一步一步地介绍如何使用 Go 语言将任何网页转换…

ASP.NET 部署到IIS,访问其它服务器的共享文件 密码设定

asp.net 修改上面的 IIS需要在 配置文件 添加如下内容 》》》web.config <system.web><!--<identity impersonate"true"/>--><identity impersonate"true" userName"您的账号" password"您的密码" /><co…

基础IO2

文章目录 磁盘结构磁盘存储结构磁盘的逻辑结构引入文件系统理解文件系统inode 映射 data blocks文件名与inode的关系dentry树文件描述符与进程之间的关系 深刻理解软硬链接软链接硬链接 动静态库静态库1. 手动制作静态库2.调用静态库(1)安装到系统(2)自己指定查找路径(3)自己创…

计算机网络:运输层 —— TCP的流量控制

文章目录 TCP的流量控制TCP的流量控制方法滑动窗口机制持续计时器 TCP的流量控制 当 TCP 客户端持续发送大量数据时&#xff0c;应用程序可能正忙于其他任务&#xff0c;并不一定能够立刻取走数据&#xff0c;这会造成接收方接收缓存的溢出&#xff0c;导致数据丢失。 TCP 为应…

Flink_DataStreamAPI_执行环境

DataStreamAPI_执行环境 1创建执行环境1.1getExecutionEnvironment1.2createLocalEnvironment1.3createRemoteEnvironment 2执行模式&#xff08;Execution Mode&#xff09;3触发程序执行 Flink程序可以在各种上下文环境中运行&#xff1a;我们可以在本地JVM中执行程序&#x…

鸿蒙中如何实现图片拉伸效果

2024年10月22日&#xff0c;华为发布会上&#xff0c;推出鸿蒙5.0。现在加入恰逢时机&#xff0c;你&#xff0c;我皆是鸿蒙时代合伙人。无论为了学习技术&#xff0c;还是为了谋福利&#xff0c;在鸿蒙的浩瀚海洋中分到一杯羹。现在学习鸿蒙正当时。 一文了解鸿蒙中图片拉伸的…

Unity 2022 Nav Mesh 自动寻路入门

untiy 2022 window-PackageManager-AINavigation 安装 Install 2.创建一个空物体命名Nav&#xff0c;在其自身挂载 NavMeshSurface 然后点击bake 烘焙地形即可 3.创建palyer和怪物 怪物AI代码 using System.Collections; using System.Collections.Generic; using UnityEngi…

基于gradio+networkx库对图结构进行可视化展示

前言 在gradio框架下对蛋白质-蛋白质相互作用网络&#xff08;PPI网络&#xff09;进行可视化&#xff0c;并将其在网页前端进行展示。 方法 其实很简单 可以直接使用networkx画图后保存图片&#xff0c;然后使用Gradio框架的image组件进行展示即可。 但实际上gradio还配置…