指针和引用的部分
内容:指针和引用和结构体
友元函数
1.定义方法
在函数前面加一个friend。
例如:friend void test();
2.什么是友元函数?
一般来说,对于一个类的私有成员,只有类的成员函数才可以查看和赋值。而友元函数,是指之被定义成类的友元函数之后,虽然不属于此类,但是可以访问此类的私有成员
3.分类
其他类的成员函数
不属于任何类的函数
4.具体的实现
- 参数通常是所要操纵的类的引用,如果要操纵数据数据成员,参数在根据情况添加。
- 友元函数不属于任何类时
说明:此时定义,友元函数可以定义为私有权限,但是调用时没有影响,因为它不属于类A
#include <bits/stdc++.h>
using namespace std;
class A
{
private:
int i;
friend void FriendFun(A *, int);
public:
void MemberFun(int);
void show();
};
void FriendFun(A *ptr, int x)
{
ptr->i = x;
}
void A::MemberFun(int x)
{
i = x;
}
void A ::show()
{
cout<<i<<endl;;
}
int main()
{
A a;
a.MemberFun(10);
a.show();
FriendFun(&a,20);
a.show();
}
- 友元函数是其他类的成员函数
说明:在这个程序中,有两个类,希望Time类的display函数可以输出本类的数据成员以及Date中的数据成员。但是Date类的数据成员无法直接在TIme类的函数中调用,为了实现这一功能,需要将这个输出函数定义为Date类的友元函数。
声明式:friend void Time::display(Date &);
在类外实现时,和普通的成员函数相同:
void Time::display(Date &d) { }
#include <iostream>
using namespace std;
class Date; //对Date类的提前引用声明
class Time //定义Time类
{
public:
Time(int, int, int);
void display(Date &);
private:
int hour;
int minute;
int sec;
};
class Date //声明Date类
{
public:
Date(int, int, int);
friend void Time::display(Date &); //声明Time中的display函数为友元成员函数
private:
int month, day, year;
};
Time::Time(int h, int m, int s) //类Time的构造函数
{
hour = h, minute = m, sec = s;
}
void Time::display(Date &d)
{
cout << d.month << "/" << d.day << "/" << d.year << endl;
cout << hour << ":" << minute << ":" << sec << endl;
}
Date::Date(int m, int d, int y)
{
month = m;
day = d;
year = y;
}
int main()
{
Time t1(10, 13, 56);
Date d1(12, 25, 2004);
t1.display(d1);
return 0;
}
- 注意点:友元函数除了在重载运算符时使用,其他情况下不要使用
友元类(不建议使用破坏了封装性)
如果A类的所有数据成员对B开放,则将B声明为A的友元类
#include<iostream>
using namespace std ;
class A
{ friend class B ;
public :
void Display()
{ cout << x << endl ; } ;
private :
int x ;
} ;
类的组合
- 使用情况
因为友元类破坏了类的封装性,所以不建议使用。当我们需要去操作其他类的数据时,可以将类作为一个成员变量,用组合的方式进行操作 - 类的声明形式
class studnet{
private:
string name;
grade g;//grade类的对象作为student类的数据成员
public:
……
};
class grade{
private:
char level;
public:
void show()
{
sout<<level<<endl;
}
}
-
构造函数
1.当一个类中含有已经定义的类类型成员,带参数的构造函数对数据成员初始化,须使用初始化语法形式(初始化顺序只和声明顺序有关)。如果没有使用初始化列表进行初始化,则调用无参的构造函数(因此,一个类中如果有代餐的构造函数,也必须写午餐的构造函数)。析构顺序和声明顺序相反
2.在主函数创student对象时,会先调用grade的构造函数,在调用student构造函数 -
注意
为了保护类的封装性,grade类的变量是私有的,所以如果想要获得以及更改变量,必须书写get/set函数。 -
应用
类的组合常应用于写信息管理系统,在操作类中调用数据类的成员
重载运算符
- 重载普通运算符
- 重载输入输出运算符
1、可以重载的运算符和不能被重载的运算符
2、重载的注意点
- 重载运算符函数可以对运算符作出新的解释,但原有基本语义不变:
也就是说重载运算符号后不要修改我们所熟知的一些规范,让运算符从狭义的运算符变成广义的运算符.
不改变运算符的优先级
不改变运算符的结合性
不改变运算符所需要的操作数
不能创建新的运算符
3、重载运算符可以重载为成员函数中或者友元函数。
4、大部分的运算符都可以通过成员函数和友元函数重载。
- = () [] -> 不可以通过友元函数重载运算符。
- 如果运算符的操作运算数的类型不同,或者说第一个操作数需要隐式的转换,此时需要需要友元函数重载运算符。
- 对于可以重载为两种形式的运算符,我们选择使用成员函数的方法。因为友元破坏了类的封装性。
5、具体的重载方法
成员函数重载运算符代码形式
//成员运算符函数的原型在类的内部声明格式如下:
class X {
//…
返回类型 operator运算符(形参表);
//…
}
//在类外定义成员运算符函数的格式如下:
返回类型 X::operator运算符(形参表)
{
函数体
}
说明、
- 1、对象做参数,一般采用引用方式传参
2、参数比操作数少一个,并且参数一般是对象,而且是引用的对象(如果不是引用,则形参是临时对象,离开函数便被销毁),最好是常对象(因为操作数一般是用来提供信息的,不是用来改变的)。对于双目运算符,左边的操作数默认是当前对象,通过this指针传递
3、函数体
重载算术运算符就是将复杂数据类型中的简单数据类型进行运算符的操作。所以一般来讲函数体是将需要操作的成员变量用运算符操作一遍。
4、返回一般为值为当前对象
友元函数重载运算符
- 因为友元函数没有this指针,所以使用友元函数重载运算符,有几个操作数写几个参数
- 代码形式
在类内的声明class A{ friend A operate 运算符([const] 对象引用……)//A是返回的参数 };
类外的定义形式A operate 运算符([const] 对象引用……){}
6、调用
直接使用运算符,和平时使用一样
注意
- 对于单目运算符自加自减
因为单目运算符++,- -有前置和后置的区别,所以声明方法稍有不同
Time operator++( ); //声明前置自增运算符“++”重载函数
Time operator++(int); //声明后置自增运算符“++”重载函数
具体定义
Time Time∷operator++( ) //定义前置自增运算符“++”重载函数
{
if(sec++>=60) {
sec-=60; //满60秒进1分钟
minute ++;
}
return *this; //返回当前对象值
}
Time Time∷operator++(int) //定义后置自增运算符“++”重载函数
{
Time temp(*this);//temp是临时对象,将原来的对象赋值给temp。因为是后置,++后的整体值应该不变
sec++;
if(sec>=60) {
sec-=60;
++minute;
}
return temp; //返回的是自加前的对象
}
- 典型的成员函数重载运算符:重载赋值运算符,只能通过成员函数重载
1、声明
类名 & 类名 :: operator= ( 类名 ) ;
重载输入输出运算符
1、重载此类运算符,要将其重载为友元函数。
在类内的声明
class vector
{ public :
vector( int size =1 ) ; ~vector() ;
int & operator[] ( int i ) ;
friend ostream & operator << ( ostream & output , vector & ) ;
friend istream & operator >> ( istream & input, vector & ) ;
private :
int * v ; int len ;
};
2、在类外的具体实现
- 重载输出运算符“<<”(只能被重载成友元函数,不能重载成成员函数)
ostream& operator<<(ostream& out,class_name& obj)
{
out<<obj.item1;
out<<obj.item2;
.. .
out<<obj.itemn;
return out;
}
- 重载输入运算符“>>” (只能被重载成友元函数)
istream& operator>>(istream& in,class_name& obj)
{
in>>obj.item1;
in>>obj.item2;
. . .
in>>obj.itemn;
return in;
}
3、<< 和 show()方法的区别
意义上不同。show方法是调用方法,而重载后的输入输出运算符,可以将对象作为一个变量进行输出
4、再进行输入或输出时,可以进行合法性检测
静态成员变量和函数static
一、
1、静态成员又称为类成员。不属于某一个对象,所以针对某一对象的this不能用于静态成员
2、静态数据成员
对于普通的变量,每一个对象都有一个关于变量的副本。对于类变量,所有的对象共享这个数据,都可以进行查看和修改。
3、静态成员函数
静态成员函数是为了操作静态成员变量存在的
二、静态成员变量
1、
2、初始化
在类外进行初始化。例如Counter类有一个静态变量smem
Counter :: smem=0;
3、公有权限的访问
- 公有权限的数据成员
类名::静态成员的名字
对象名.静态成员名字
对象指针->静态成员的名字
在静态成员函数内部,直接访问。
4、注意
- 不能在成员初始化列表中进行初始化
- 如果未进行初始化,则编译器自动赋初值(默认值是0)
- 私有成员是无法在类外访问的
三、静态成员函数
1、静态函数仅可以访问静态成员(静态成员函数或是静态数据成员)
2、声明:static 返回类型 静态成员函数名(参数表);
3、调用
类名::静态成员函数名(实参表)
对象. 静态成员函数名(实参表)
对象指针->静态成员函数名(实参表)
4、注意
如果要用静态成员函数访问普通成员变量,需要使用对象。给静态成员函数添加一个对象参数。在函数内访问此对象的普通变量
class small_cat{
public:
small_cat(double w){
weight=w;total_weight+=w;total_number++;
}
static void display(small_cat &w)
{
cout<<"The small_cat weights "<<w.weight<<"kg\n";
}
static void total_disp()
{
cout<<total_number<<"small_cat total weight ";
cout<<total_weight<<" kg "<<endl;
}
private:
double weight;
static double total_weight; static double
常成员
常成员变量
常成员函数
对于常成员变量,我的第一反应会想到初始化列表。
1、常成员变量,通过初始化列表赋值后,不能再改变数值
2、常成员函数
- 声明: 类型说明符 函数名(参数表) const;
- 注意:
常成员函数不能修改常数据成员(这是肯定的),也不能调用非const修饰的成员函数,但是可以调用const修饰的成员函数和静态函数和构造函数
3、常对象:就是一个生成对象,只不过是常对象,其值为常量
如果在说明对象时用const修饰,则被说明的对象为常对象。
常对象的说明形式如下:
类名 const 对象名[(参数表)];
或者
const 类名 对象名[(参数表)];
在定义常对象时必须进行初始化,而且不能被更新。
说明:
(1)C++不允许直接或间接更改常对象的数据成员。
(2)C++规定常对象只能调用它的常成员函数、静态成员函数、构造函数(具有公有访问权限)。
复制构造函数
- 复制构造函数调用情况
1、声明语句中,用一个类的对象去初始化另一个对象
2、当一个对象作为函数的实参传递给形参时,初始化形参对象
3、当对象是函数的返回值时,由于需要生成一个临时对象作为函数返回结果,系统需要将临时对象的值初始化另一个对象,需要调用复制构造函数。
- 系统有自带的赋值构造函数,如果没有重写,会调用此函数。但是此函数是浅赋值。
- 浅赋值:只复制变量的内容,并不复制变量指向的资源
- 深复制:深复制针对的是指针。对于对象的指针变量,如果只是用浅赋值,释放其中一个变量后,另一个变量便无从指向。
通过重新申请空间,将原来的对象的内容一个个的赋值。尤其是对于指针类型。因为指针存的是地址,他有所这项的资源。为了避免资源被销毁,需要将资源进行复制 - 定义代码:
定义:类名::类名([const] 类名 &对象名);
class A{
A(const 对象引用);
//或者
A(const 类类型的引用)
{}
}
A::A(const & a)
{}
- 调用方法
int main()
{
//对于使用已知对象来初始化另一个对象
A a;//已知对象
A b(a);
A*c;
c=new A(a);
//或者
A b=a;
A*c=&a;
}
- 和重载赋值运算符区别
前提条件: A a;
如果是A b=a;
或者是A b=a;
则为调用复制构造函数
如果是A b;b=a;
重载赋值运算符
构造函数
- 作用:创建对象的成员函数,为对象申请空间。并且对数据成员进行赋值
- 当没有定义构造函数时,系统会自动提供一个缺省的构造函数,(即:没有参数和函数体)
- 形式:函数名和类名相同,可以有参数,但是没有返回值
1、在类内定义
public 类名([参数])//参数是可选项
{
函数体
}
2、在类外定义
类名 :: 类名([参数])
{
函数体
}
- 注意,这点非常重要
当我们没有定义构造函数时,系统会自动调用一个无参无函数体的构造函数。但是如果定义了一个构造函数,无论是什么样的,系统的构造函数便不再被使用。
也就是说,当我们定义了一个带参数的构造函数,但是没有定义无参的构造函数,在生成对象时,如果想使用无参构造函数进行实例化,便会报错。只能使用我们定义过的构造函数实例化。因此,在一个类中,保险起见,构造函数至少定义两个,包含一个无参数的。 - 初始化列表
1、形式:
class Date{
int day,month,year;
Date (int d,int m,int y):day(d),month(m),year(y)
{}
};
2、使用场景:
数据成员是常量
数据成员是引用了类型
数据成员是没有无参构造函数的类的对象
原因就是成员变量在类的定义时不能初始化。
3、例子:
#include <iostream>
using namespace std;
class A{
public:
A(int i):x(i),rx(x),pi(3.14)
{}
void display()
{cout<<"x="<<x<<"rx="<<rx<<"pi="<<pi<<endl;}
private:
int x,℞
const float pi;
};
int main(){
A aa(10);
aa.display();
return 0;
}
class B
{ public :
B( int x, int y ) : aa( x ), b( y ) { }//aa(x):相当于A的构造函数实参
void out()
{ cout << "aa = " << aa.a << endl << "b = " << b << endl ; }
private :
int b ;
A aa ;
} ;
4、注意:初始化列表赋值时,按照数据成员的声明顺序,依据初始化列表进行赋值,不是构造函数时的顺序。
比如说如下代码:解释在代码后面的说明
#include <iostream>
using namespace std;
class CMyClass{
public:
CMyClass(int x, int y):m_y(x),m_x(m_y)//注意:给m_x赋值的是m_y,不是y
{
cout<<"m_x="<<m_x<<endl;
cout<<"m_y="<<m_y<<endl;
}
private:
int m_x,m_y;
};
int main()
{
CMyClass mc(15,10);
return 0;
}
注意:给m_x赋值的是m_y,不是y
注意:给m_x赋值的是m_y,不是y
注意:给m_x赋值的是m_y,不是y
说明:先给m_x赋值,但是m_x的值是m_y,但是m_y还没有赋值,因此系统随机赋值。然后给m_y赋值为x
学习感受
第一、回顾自己最近的学习,我发现自己最大的一个问题是听完课之后没有及时整理所学习的内容。我想的是听完课后将老师讲的内容趁热打铁,整理一遍。但是每次下课后一休息就忘了。就整理了一次。感觉有点对不起自己。我会想个办法,让自己可以下课后及时整理所学内容。
第二,第二次作业的时候,我为了尽快写完,有一节课没有听,然后又看的回放,结果作业并没有完全达到老师的要求,而且还拉下了一节课,十分不划算。以后不会这样做了。每次上课老师都会提一些建议和技巧,我会听取老师的建议,完善作业。
第三、我在写作业的过程会查一些资料,发现对于一些c语言学的东西还没有掌握。通过查漏补缺,完善了知识体系。
第四、真心觉得老师讲得好,特别适合我的大脑回路
第五、功能的定义来源是需求。写程序时可以先写主函数,在写类的功能的具体实现