0
点赞
收藏
分享

微信扫一扫

c#实现数据导出为PDF的方式

WikongGuan 2024-08-27 阅读 8

 Welcome to 9ilk's Code World

       

(๑•́ ₃ •̀๑) 个人主页:        9ilk

(๑•́ ₃ •̀๑) 文章专栏:     与C++的邂逅 



🏠 类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6 个默认成员函数。

class Date {}; //空类

注:这里的“默认”与之前讲的“缺省”意思类似,意思是用户没有显示实现,编译器会自动生成。

🏠 构造函数

📌 构造函数概念

对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置

信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?如果有时忘记先调用Init初始化,这时会导致日期是随机值,可能会导致其他麻烦,这时C++提供了构造函数这个解决方案。

class Date
{
public:
 void Init(int year, int month, int day)
 { 
  _year = year;
  _month = month;
  _day = day;
 }

 void Print()
 {
   cout << _year << "-" << _month << "-" << _day << endl;
 }

private:
 int _year;
 int _month;
 int _day;
};

int main()
{
  Date d1;
  d1.Init(2022, 7, 5);
  d1.Print();
  Date d2;
  d2.Init(2022, 7, 6);
  d2.Print();
  return 0;
}

注:构造函数类似Init函数的功能,它是特殊的成员函数,它的主要人物不是开空间创建对象,而是跟Init功能一样初始化对象。

📌 构造函数特性

  • 构造函数函数的函数名与类名相同。

  • 构造函数无返回值,但不是说返回值是void,不需要写void。

  • 对象实例化时编译器自动调用对应的构造函数。(与C语言的Init函数不同,这里是自动调用)

  • 构造函数可以进行函数重载。也就是说可以写多个构造函数,多种初始化方式,需要几个写几个。
class Date
{

public:
    Date() //无参构造
    {
        _year = 1;
        _month = 1;
        _day = 1;
    }

    Date(int year,int month,int day) //有参构造
    {
        _year = year;
        _month = month;
        _day = day;
    }

    Date(int year = 2, int month = 3, int day = 4) /全缺省
    {
        _year = year;
        _month = month;
        _day = day;
    }

private:
    int _year;
    int _month;
    int _day;
};
int main()
{ 
    Date d;            //无参构造 无参调用
    Date d1(2, 1, 1); //有参构造 对象+参数列表 有参调用
    return 0;
}

注:对于无参构造来初始化对象,规定是不能带括号,这样与函数声明分割开;而有参构造要带括号+参数列表。

  • 默认构造:不传参就可以调用的构造并且默认构造函数只能有一个
  • 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦

    用户显式定义编译器将不再生成

我们可以看到,当我们未显示定义构造函数时,编译器会自动生成一个构造函数,它对成员的处理如下:

注意: C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即: 内置类型成员变量在

类中声明时可以给默认值

class Date
{
private:
 // 基本类型(内置类型)
 int _year = 1970;
 int _month = 1;
 int _day = 1;
};

这种默认构造有什么意义呢?


class MyQueue
{
private:
Stack _pushst;
Stack _popst;

};

对于这种成员都是自定义类型的成员,此时自动生成的构造就可以对自定义类型成员调用默认构造,不需要我们自己动手。

🏠 析构函数

📌 析构函数概念

📌 析构函数特性

  • 析构函数名是在类名前面加上字符~。
class Date
{
public:
   ~Date(); //析构    
};
  • 析构函数无参数无返回值。
  • 对象生命周期结束时,C++编译系统自动调用析构函数,当然他也能显示调用。

  • 一个类只能有一个析构函数(没有参数也就不支持重载)。若未显示定义,系统会自动生成默认的析构函数。

我们可以看到当用户为显示定义析构函数时,会对它的成员做以下处理:

  • 不是所有的类都需要显示写析构函数,若类中有不属于对象的资源(如在堆上申请空间)则需要写析构进行清理。
//没有资源需要清理
class Date
{
 private:
  int _year;
  int _month;
  int _day;  
}

//有资源需要清理
class MyQueue
{
 private:
   Stack pushst;
   Stack popst;
}
  • 关于构造和析构的顺序

🏠拷贝构造函数

📌 拷贝构造函数概念

拷贝构造两种写法:

class Date
{
public:
  

private:
   int _year;
   int _month;
   int _day;
};

 Date(const Date& d)   // 错误写法:编译报错,会引发无穷递归
 {
  _year = d._year;
  _month = d._month;
  _day = d._day;
 }

int main()
{
  Date d1; 
//拷贝构造
  Date d2(d1); 
  Date d3 = d1;
  return 0;
}

📌 拷贝构造函数特性

  • 拷贝构造函数是构造函数的一个重载形式,它的函数名也与类名相同,显示实现拷贝构造后编译器就不生成默认构造了
  • 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,

    因为会引发无穷递归调用。

调用拷贝构造就需要传参,我们这里传参是传值传参,C++规定自定义类型传值传参要调用拷贝构造,语法逻辑上循环往复造成无穷递归,但编译器会进行强制检查不允许这样用。

Date(Date* d)
{
  _year = d->_year;
  _month = d->_month;
  _day = d->_day;
}

若要完成拷贝用指针实现的构造也是可以实现的,但是规定拷贝构造参数是引用,而且引用比指针使用起来更方便。

Date(Date& d)   
 {
   d._year  = _year;
   d._month = _month;
   d._day   = _day;
 }

Date(const Date& d)   
 {
    _year = d._year;
   _month = d._month;
   _day = d._day;
 }

  • 若未显式定义,编译器会生成默认的拷贝构造函数 默认的拷贝构造函数对象按内存存储按

    字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝

  • 编译器自动生成的拷贝构造实现的是浅拷贝而不是深拷贝。

解决方法:深拷贝

此时我们可以开一个跟s1指向空间一样大小的空间,再将里面的值一个一个拷贝到新开空间,再让s2指向这块空间。

  • 拷贝构造函数典型调用场景

🏠 赋值运算符重载

📌 运算符重载

我们之前C语言对两个自定义类型的比较需要我们自己写一个函数,但是这样代码的可读性较差,为此C++引入了运算符重载,让自定义类型可以像内置类型一样可以直接使用运算符进行操作。

class Date
{
public:
 int _year;
 int _month;
 int _day;
};

//比较日期的小于
bool compare(const Date& d1,const Date& d2)
{
   if(d1._year < d2._year)
      return true;
   else if(d1._year == d2._year)
   {
       if(d1._month < d2._month)
          return true;
       else if(d1._month == d2._month)
        {
            if(d1._year < d2._year)
                return true;
            return false;
        }
       else 
          return false;   
   }
   return false; 
} 

//运算符重载

bool opertaor<(const Date& d1,const Date& d2)
{
   if(d1._year < d2._year)
      return true;
   else if(d1._year == d2._year)
   {
       if(d1._month < d2._month)
          return true;
       else if(d1._month == d2._month)
        {
            if(d1._year < d2._year)
                return true;
            return false;
        }
       else 
          return false;   
   }
   return false; 
} 

cout << Compare(d1,d2)<<endl; //可读性差
cout << (d1 < d2) <<endl; //易于理解可读性强

函数名 : 关键字operator +  需要重载的运算符符号。

函数原型 :返回值类型 operator操作符 (参数列表)

注意:

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 运算符重载的返回值与要重载的运算符有关,一个类要重载哪些运算符是看需求,看重载有没有价值和意义
  • 重载操作符必须有一个类类型参数,运算符重载是为了自定义类型而生的参数是自定义类型就没意义了

int operator-(int,int);//错误
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义。

  • .*   ::   sizeof   ?:   . 这五个运算符是不能被重载的

    class ob
    {
     void fcun(){}
    };//类ob内有函数func;
    
    typedef void(ob::*pobfunc)(); //成员函数指针类型
    
    int main()
    {
    
    pobfunc  fp = &ob::func;//定义成员函数指针p指向函数func
    
    ob temp;//定义ob类对象temp
    
    (temp.*fp)();使用对象temp加上.*运算符调用fp指向的成员函数
    
    }

  • 运算符重载的两种调用方式:显示调用和转换调用
Date d1;
Date d2;
cout << operator==(d3,d4) <<endl;//显示调用
cout << (d3 == d4) <<endl; //转换调用
  • 运算符重载成全局函数,无法访问私有成员的问题
class Date
{
private:
  int _year;
  int _month;
  int _day; 
};

bool operator==(const Date& d1,const Date& d2)
{
    return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}

由于operator==是在全局域重载的,但成员是私有的,在类外不能访问因此我们需要其他解决方法。

a. 提供这些成员的get或set

class Date
{
public:
  int Getyear()
  {
    return _year;
  }
//Getmonth() Getday()....
private:
	int _year;
    int _month;
    int _day;
};

b.使用友元

c.重载成成员函数(一般用这种)

  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐

    藏的this。毕竟运算符重载 参数个数要与重载的操作符的操作数一致。

class Date
{
public: 
    bool operator==(const Date& d1,const Date& d2);//参数个数为3 但==操作数应该为2
    bool operator==(const Date& d) //隐含的this 参数个数实际为2
    {
      //....
    }
private:
	int _year;
    int _month;
    int _day;
};

重载成成员函数后的调用,我们常用转换调用。

Date d1;
Date d2;
cout << d1.operator==(d4)<<endl; //显示调用成员函数
cout <<( d1 == d2 ) << endl;//本质还是转换成显示调用

注:如果此时又在全局重载还是会优先调用在类里面重载的,这也说明此时在全局重载没有意义。

  • 运算符重载中,参数顺序和操作顺序是一致的。

📌 赋值运算符重载

Date d1;
Date d2;
d2 = d1;

对于这段代码,d2==d1并不是调用拷贝构造,而是调用赋值运算符重载。对于赋值运算符重载我们需要注意以下几点:

  • 拷贝构造 vs 赋值重载
  • 参数类型 const T&,传递引用可以提高传参效率。

  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值。
  • 传值返回 vs 传引用返回

传值返回

传引用返回

//这里没返回局部或临时对象 传引用返回效率高
Date& operator=(const Date& d)
{
 _year = d._year;
 _month = d._month;
 _day = d._day;
 return *this;
}
  • 检测是否自己给自己赋值
Date& operator=(const Date& d)
{
 if(this != &d)
 {
  _year = d._year;
  _month = d._month;
  _day = d._day;
 }
 return *this;
}
  • 返回*this :要符合连续赋值的含义。

  • 赋值运算符只能重载成类的成员函数不能重载成全局函数(规定),因为它是默认的成员函数。

class Date
{
public:
 Date(int year = 1900, int month = 1, int day = 1)
 {
 _year = year;
 _month = month;
 _day = day;
 }
 int _year;
 int _month;
 int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{
 if (&left != &right)
 {
 left._year = right._year;
 left._month = right._month;
 left._day = right._day;
 }
 return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

  • 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝

默认赋值重载意义:

  • 对于内含引用成员的class,重载赋值操作符并无意义,因为引用不可改变指向,指向其他对象。

🏠 const成员函数

📌 const成员函数

class Date
{
public:
    void Print()const
    {
        cout << _year << " " << _month << " " << _day << endl;
    }

    void Print(const Date* this) //编译器处理
    {
        cout << this->_year << " " << this->_month << " " << this->_day << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

📌 const 与 非const比较

【面试题】

1. const对象可以调用非const成员函数吗?

2. 非const对象可以调用const成员函数吗?

3. const成员函数内可以调用其它的非const成员函数吗?

4. 非const成员函数内可以调用其它的const成员函数吗?

注:const对象要在定义时就给他值。

🏠 取地址以及const取地址操作符重载

class Date
{ 
public :
 Date* operator&()
 {
  return this ;
 }
 const Date* operator&()const
 {
  return this ;
 }
private :
 int _year ; // 年
 int _month ; // 月
 int _day ; // 日
};

class Date
{ 
public :
 Date* operator&()
 {
  return nullptr ;
 }
 const Date* operator&()const
 {
  return (const Date*)0xffffffff ;
 }
private :
 int _year ; // 年
 int _month ; // 月
 int _day ; // 日
};

🏠 Date类的实现

📌 日期类大小比较

    bool operator<(const Date& d) const;
	bool operator<=(const Date& d) const;
	bool operator>(const Date& d) const;
	bool operator>=(const Date& d) const;
	bool operator==(const Date& d) const;
	bool operator!=(const Date& d) const;
  • operator < 

参考代码:

bool Date::operator<(const Date& d)
{
	if (_year < d._year)
	{ //年小直接日期小
		return true;
	}
	else if (_year == d._year)
	{//年等再比月
		if (_month < d._month)
		{
			return true;
		}
		else if (_month == d._month) //月等再比日
		{
			return _day < d._day;
		}
	}
	return false;
}
  • operator ==

判断两个日期相等只需要判断他们的年月日是不是都相等。

bool Date::operator==(const Date& d)
{
	return _year == d._year && _month == d._month && _day == d._day;
}
  • operator <=

小于等于只要判断这个日期是否是小于或等于另一个日期即可,我们直接复用前两个实现的小于和等于。

bool Date::operator<=(const Date& d) 
{
	return (*this) < d || (*this) == d;
}
  • operator >

我们知道大于的对立事件,所以取<=的逻辑反就是大于,此时可以复用上一个实现的<=。

bool Date::operator>(const Date& d) 
{
	return !((*this) <= d);
}
  • operator >=

大于等于的对立事件是小于,所以我们直接复用<即可。

bool Date::operator>=(const Date& d) 
{
	return !((*this) < d) ;
}
  • operator !=

不等于的对立事件是等于,我们直接复用==即可。

bool Date::operator!=(const Date& d)
{
	return !((*this) == d);
}

📌 日期 += 天数

参考代码:

class Date
{
//...
  int GetMonthDay(int year, int month)
{ 
  assert(month > 0 && month < 13);
  static int monthDayArray[13] = { -1, 31, 28, 31,30,31,30,31,31,30,31,30,31};//下标从0开始 
                                                                               // 不好处理
// 闰年
  if (month == 2 && (year % 4 == 0 && year % 100 !=0) || (year % 400 == 0))
  {
    return 29;
  }
  else
  return monthDayArray[month];
}

//...
};


Date& Date::operator+=(int dat)
{
  _day += day;
  while(_day > GetMonthDay(_year,_month))
  {
     _day -= GetMonthDay(_day,_month); //满足当前月后剩下的天数
     ++_month; //超出当前月
     if(_month == 13)
     {
        ++_year;  
        _month = 1;  
     }
  }
  return *this;
}

📌 日期 + 天数

Date Date::operator+(int day) const
{
	Date temp(*this);//拷贝
	temp += day; //复用
	return temp;
}

既然逻辑一样,那是否可以先实现+,再让+=复用+呢?

Date Date::operator+(int day) const
{
	Date temp(*this);//拷贝
	temp._day += day;
  while(temp._day> GetMonthDay(temp._year,temp._month))
  {
     temp._day-= GetMonthDay(temp._day,temp._month); //满足当前月后剩下的天数
     ++temp._month; //超出当前月
     if(temp._month == 13)
     {
        ++temp._year;  
        temp._month = 1;  
     }
  }
	return temp;
}

Date& Date::operator+=(int day)
{
   *this = *this + day;
   return *this; 
}

📌 日期 -= 天数

参考代码:

Date& Date::operator-=(int day)
{
	_day -= day;
	//此时该月的减完了
	while (_day < 0)
	{
		 //用上一个月的来补
		--_month;
		if (_month == 0)
		{
			_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 == 13)
		{
			_year++;
			_month = 1;
		}
	}
	return*this;
}

Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return (*this) += -day;
	}
	_day -= day;
	//此时该月的减完了
	while (_day < 0)
	{
		 //用上一个月的来补
		--_month;
		if (_month == 0)
		{
			_year--;
			_month = 12;
		}
		_day += GetMonthday(_year, _month);
	}
	return *this;
}

📌 日期 - 天数

跟日期+天数一样复用即可。

Date Date::operator-(int day)
{
	Date temp(*this);
	temp -= day;
	return temp;
}

📌 前置++

//前置++ 先加
Date& Date::operator++()
{
	(*this) += 1;
	return *this;
}

📌 后置++

重载前置++之前我们需要理清函数重载与运算符重载之间的关系。

Date operator-(int day);

int operator-(const Date& d);

对于前置++和后置++,他们用的都是同一运算符所以为了区分他们构成重载,设计者强行给后置++的重载增加了一个int形参,只是单纯为了区分。

// 这里不需要写形参名,因为接收值是多少不重要,也不需要用
// 这个参数仅仅是为了跟前置++构成重载区分
Date Date::operator++(int)
{
	Date temp(*this);
	*this += 1;
	return temp;
}

📌 日期-日期

int Date::operator-(const Date& d) const
{
	int flag = 1;
	Date max = *this;
	Date min = d;
	int n = 0;
	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}
	while (min != max)
	{
		min++;
		n++;
	}
	return n*flag;
}

📌 流插入重载

  • 内置类型使用流插入
int i = 0 ;
double d = 1.1;
cout << i << " "<< d <<endl;

我们平常使用的cou其实是个ostream对象,为什么可以直接cout << 内置类型对象呢?

  • 自定义类型
void Date::operator<<(ostream& out)
{
  out << _year << "年" << _month << "月" << _day << "日"<<endl;
} 

d1.operator<<(cout);//传ostream对象
d1 << cout;

有同学可能会好奇为什么转换调用时,Date类是做左操作数?

在重载成全局前我们需要知道我们成员变量一般是设置成私有的,我们若想在全局访问他们,除了写一个Get或Set,我们可以将流插入重载设置为友元。友元即这个类的朋友,也可以访问这个类成员。


class Date
{
//...
friend void operator<<(ostream& out, const Date& d);

}

void operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;	
}

还有不足的是,上面返回值是void的话,如果我们想连续打印多个日期类对象,由于是从左向右结合,所以我们需要返回一个ostream对象继续进行打印。

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

📌 流提取重载



bool Date::CheckDate()
{
	if (_month < 1 || _month > 12 || _day <1 || _day > GetMonthday(_year, _month))
	{
		return false;
	}
	return true;
}



istream& operator>>(istream& in, Date& d)
{
	cout << "请依次输入年月日:>";
	in >> d._year >> d._month >> d._day;

	if (!d.CheckDate())
	{
		cout << "日期非法" << endl;
	}
	return in;
}

📌 构造函数

Date::Date(int year, int month, int day)
{
	// 检查日期的合法性
	if(CheckDate())
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "非法日期" << endl;
		cout << year << "年" << month << "月" << day << "日" << endl;
	}
}

📌 何时设置const成员函数

bool operator<(const Date& d) const;
bool operator<=(const Date& d) const;
bool operator>(const Date& d) const;
bool operator>=(const Date& d) const;
bool operator==(const Date& d) const;
bool operator!=(const Date& d) const;

举报

相关推荐

0 条评论