【c++基础】无指针类的设计和规范

本文详细讲解了C++中的头文件和无指针类声明,防卫式声明,模板类的使用,内联函数、构造函数、重载(包括常量成员函数、参数和返回值)、友元以及操作符重载(如+和<<)的规则和注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

头文件和无指针类

以complex类(复数)为例

声明(declaration)

注意撰写规范

防卫式声明(guard)

避免出现引用顺序错乱的问题,避免重复的引用

//complex.h
#ifndef __COMPLEX__ //若未定义“__COMPLEX__”
#define __COMPLEX__ //则定义“__COMPLEX__”
//... 0   
//... 1
//... 2
#endif
前置声明 //… 0

(forward declarations)

#include<cmath>
class ostream;
class complex;
//看不懂👇
complex&
    __doapl(complex& ths, const complex r);
类-声明 //… 1

(class declarations)

complex.h

*模板
//模板
template<template T>//告诉编译器,T代表一个未定的type
class complex{ 
  public:
    //...  
  private:
    //double re,im;//double类型的成员,实部,虚部
    T re,im;//模板类型的成员
}

创建对象时,如下使用

complex<double>c1(1.3,4.5);//1.3+i4.5
complex<int>c2(1,4);//1+i4
内联函数

(inline)

很像宏,但有区别。

特点:运算快,仅适用于简单、短小的函数,无法用于结构复杂函数(包含递归、switch等)

标志:在class的body内定义成为inline候选人

(相当于告诉编译器:你尽量将其编译为inline。

class complex{ 
  public:
    complex(double r=0,double i=0):re(r),im(i){}//inline
    double real() const{return re;}//inline
    double imag() const{return im;}//inline
  private:
    //...
}
inline double//不在本体内定义的,可以加上“inline”关键词
imag(const complex& x){
    return x.imag();
}
访问级别

(access level)

class complex{ 
  public://所有函数外界可以调用
    complex(double r=0,double i=0):re(r),im(i){}
    double real() {return re;}//外界可通过这种方法查看成员,而不能对其做修改
    double imag() {return im;}
  private://只有类自己可以看到
    double re,im;//成员(data)多为private
}
//protected也是一种权限

使用

complex c1(2,1);
cout<<c1.real();//public函数
//cout<<c1.re 错误的
构造函数

(constructor, ctor)

功能

用于创建对象

  • 函数名称和类相同
  • 参数根据需要设计
  • 可以设置默认值,如下(0,0)。
  • 没有返回类型(不需要)
  • 独有的语法初值列(initialization list,初始列)。强烈建议使用这一种语法,区分【初始化】与【赋值】,且效率较高。

因为不会考虑在后续程序中”调用构造函数“,而只是用于创建对象,或许也是其与类名必须一致的原因之一。

特殊语法
class complex{ 
  public:    
    //default argument 默认实参↓
    complex(double r=0,double i=0)
        :re(r),im(i)//初值列
        {}
        //{re=r;im=i;}//赋值(assignment)
        
    //...
  private:
    double re,im;
}

创建一个类对象

complex c1(2,1);//2+i1
complex c2;//0+i0
complex* p=new complex(4);//指向4+i0的指针

一般使用:如果需要创建对象,不可以放在private区域(无法被外界调用)

但设计模式(Singleton)需要把构造函数放在private区:外界仅可用一份

class A{
  public:
    static A& getInstance();//只能通过这个函数调用A对象
    setup(){...}
  private:
    A();
    A(const A& rhs);
    //...
};
A& A:getInstance(){
    static A a;//这个类自己准备了一份自己的对象a
    return a;
}
重载

(overloading)

之所以可以完成函数重载,是因为编译器在编译函数时,会编译

【函数名】、【参数类型】、【参数个数】

class complex{ 
  public:    
    complex(double r=0,double i=0):re(r),im(i){}//①
    complex ():re(0),im(0){}//② 错误的重载
    complex (double r):re(r),im(0){}//③ 正确的重载
    
    double real() const{return re;}//
    void real(double r) {re=r;}//正确的重载
    double imag() const{return im;}
  private:
    double re,im;
}

①有默认参数,而②无参数。因此不可这样重载。

(不传参时,仍会调用①)

常量成员函数

(const member function)

只读:让外界不能改变数据

class complex{ 
  public:
    complex(double r=0,double i=0):re(r),im(i){}
    double real() const {return re;}//注意这里的const
    double imag() const {return im;}//(只读)
  private:
    double re,im;
}
const complex c1(2,1);
cout<<c1.real();//无法调用非常量成员函数

如果在声明const对象,就是在创建一个“不能被修改的类对象”,而在调用非常量成员函数时,编译器会将这个信息进行宣告:成员变量可能被修改,与const对象冲突,从而无法调用。

参数传达

(pass by value or pass by reference (to const))

  • 尽量不要pass by value:占用空间太大,几个byte就传递几个byte

  • 尽量使用pass by reference:传递引用(指针),传地址

  • 如果不希望对方对数据进行更改,则加const

class complex{ 
  public:
    //pass by value:
    complex(double r=0,double i=0):re(r),im(i){}
    //pass by reference (to const) 不希望修改
    complex& operation +={const complex&};
  private:
    double re,im;
    friend complex& __doapl (complex&, const complex&)
};

ostream&
    //pass by reference ↓        (to const)↓ 
    operation << (ostream& os, const complex& s){
    //...
}

尽量不要pass by value:占用空间太大,几个byte就传递几个byte

尽量使用pass by reference:传递引用(指针),传地址

complex c1(2,1);
complex c2;

c2 +=c1;//pass by reference,很快 
coue<<c2;
返回值传递

(return by value or pass by reference (to const))

同样尽量传递引用

class complex{ 
  public:    
    complex(double r=0,double i=0):re(r),im(i){}
    //↓ return by reference
    complex& operation +={const complex&};
    //↓ return by value
    double real() const {return re;}
    double imag() const {return im;}
  private:
    double re,im;
    //       ↓ return by reference
    friend complex& __doapl (complex&, const complex&)
};

//↓ return by reference
ostream&
    operation << (ostream& os, const complex& s){
    //...
}
友元

(friend)

功能:可以自由且直接获取complex对象的成员

某种程度上,是打破了封装的目标。但是更快。

class complex{ 
  public:    
    complex(double r=0,double i=0):re(r),im(i){}
    // ...
  private:
    double re,im;
    //友元
    friend complex& __doapl (complex&, const complex&)
};

inline complex&
__doapl(complex&, const complex&){
    ths->re +=r.re;//自由且直接获取complex对象的private成员
    ths->im +=r.im;
    return *ths;
}

相同类的各个对象互为友元

class complex{ 
  public:    
    complex(double r=0,double i=0):re(r),im(i){}
    // ...
    int func(const complex& pa){
        return pa.re + pa.im;
    }
};

用法:

complex c1(2,1);
complex c2;
c2.func(c1);//通过c2处理c1 
析构函数

//后续补充

//通常用于带指针的类


review:类设计的规范性

  1. 成员放在private
  2. 使用构造函数的特殊语法进行构造
  3. 根据需要,限定常量函数(const)
  4. 根据需要,尽量使用引用(指针)传参/返回值

定义(defination)

complex_test.cpp

#include "complex.h" //自己写的头文件
类-定义 //… 2

(class definition)

  • class body之外的各种函数定义,需要注意是否带上类名

    className:: funcName();

tip:注意区分传引用返回引用的时机。

不可以返回形参的引用:函数结束后,形参将被销毁

class complex{ 
  public:    
    //... 构造函数
    complex& operation +={const complex&};
    //... 其他成员函数
  private:
    double re,im;
    friend complex& __doapl (complex&, const complex&)
};
成员函数

className:: funcName(){}

以操作符“+=”的重载为例(成员函数this)

使用this指针

this指针:指向调用者

inline complex&
__doapl(complex* ths,const complex& r){//friend
    ths->re+=r.re;//直接对成员操作
    ths->im+=r.im;
    return *ths;
}

inline complex&
complex::operator +=(const complex& r)
//相当于: (this,const complex& r) ,等价,但不可写this
{
    return __doapl(this,r);//方便其他函数的调用,所以包起来
}

使用this指针

complex c1(2,1);
complex c2;
c2+=c1;//c2调用了“+=”函数,是其中的this指针指向的对象
  • __doapl:标准库里复数的代码。do assignment plus(赋值的加法)
操作符重载(+=)

return by reference

  • 传递者 无需知道 接收者 是以reference的形式接收
inline complex& //返回类型是一个reference
__doapl(complex* ths,const complex& r){
    //...
    return *ths;//返回指针指向的对象,value
}

inline complex&
complex::operator +=(const complex& r){
    return __doapl(this,r);//方便其他函数的调用,所以包起来
}

思考:修改返回类型为void是否可以?

void //修改返回类型
complex::operator +=(const complex& r){
    __doapl(this,r);
}

后果:

c2+=c1;//正常使用
c3+=c2+=c1;//如果返回void,将无法使用

连续赋值,不能返回void

非成员函数

不再是成员函数

funcName(){}

inline doble
imag(const complex& x){
	return x.imag();
}

inline double
real(const complex& x){
	return x.real();
}
complex c1(2,1);
cout<<real(c1);
cout<<c1.real();//相同的输出
操作符重载 : +

(非成员函数无this)以“+”为例

考虑了操作者(client)可能使用这一操作符的种种情形

以重载运算符“+”为例。因为考虑到a+b可以发生在实数之间、复数之间、复数和实数之间,所以采用全局的设计

//c1+c2
inline complex
operation + (const complex& x, const complex& y){
    return complex(real(x)+real(y),
                   imag(x)+imag(y));
}

//c1+5
inline complex
operation + (const complex& x, double r){//加实数
    return complex(real(x)+r),
                   imag(x));
}

//7+c1
inline complex
operation + (double r,const complex& x){//加实数
    return complex(real(x)+r),
                   imag(x));
}

此时不再返回reference,因为返回的必定是local object,是一个加和的结果,而不存在一个具体的对象。

全域函数
操作符重载:<<

global function

inline complex
conj (const complex x)&{
    return complex(real(x),-imag(x));//创建临时对象
}

#include<iostream>
ostream&
operator << (ostream& os, const complex){
   	//         ↑不可以加const,每次输入输出都在改变os的状态
    return os<<"("<<real(x)<<", "<<imag(x)<<'i)';
}

使用

complex c1(2,1)
cout<<conj(c1);//输出:(2,-1i)
cout<<c1<<conj(c1);//输出:(2,1)(2,-1i)
//cout作为一个对象,被传入为“<<”的os参数
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值