设为首页 加入收藏

TOP

现代C++学习指南-类型系统(二)
2023-07-23 13:28:33 】 浏览:82
Tags:现代 习指南
也就是说两个函数,假如参数列表和返回值一样,那么它们从编译器的角度来看是等价的。当然光有它们还不够,不然怎么能出现两个参数列表和返回值一样的函数呢。一个完整的函数还需要有个函数体和函数名。所以函数一般是下面这种形式:

//常规函数形式
[constexpr] 返回值 函数名(参数列表)[noexcept]{
    函数体
    }

//返回值后置形式
auto 函数名(参数列表)->返回值

当一个函数没有函数体的时候,我们通常称之为函数声明。加上函数体就是一个函数定义。

void f(int); //函数声明
void fun(int value){  //函数定义,因为有大括号代表的函数体
    
}

以上就是函数的基本框架,接下来我们分别来看一看组成它的各部分。
先说最简单的函数名,它其实是函数这种类型的一个变量,这个变量的值表示从内存地址的某个位置开始的一段代码块。前面也说过之所以能出现两个参数列表和返回值都相同的函数,但是编译器能识别,其主要功劳就在函数名上,所以函数名也和变量名一样,是一种标识符。那假如反过来,函数名相同,但是参数列表或者返回值不同呢,这种情况有个专有名词——函数重载。基于函数是复合类型的认识,它们中只要其中一种不同就算重载。另外,在C++11,还有一种没有名字的函数,称为lambda表达式。lambda表达式是一种类似于直接量的函数值,就像13,'c'这种,是一种不提前定义函数,直接在调用处定义并使用的函数形式。
参数列表是前面类型定义的升级款。所有前面说的关于变量定义的都适用于它,三种形式的变量定义,多个变量,变量初始化等。不过,它们都有了新名词。参数列表的变量称为形式参数,初始化称为默认参数。同样形参在实际使用的时候需要初始化,不过初始化来自调用方。形式参数没有默认值就需要在调用的时候提供参数,有默认值的可以省略。

int plus(int a,int b=1){ //b是一个默认参数
    return a+b;
}

int main(void){
    int c=plus(1); //没有提供b的值,所以b初始化为1,结果是2
    int d=plus(2,2); //a,b都初始化为2,结果是4
    //int f=plus(1,2,3); //plus只有两个形参,也就是两个变量,没法保存三个值,所以编译错误
    return 0;
}

和参数列表一样,返回值也是一个变量,这个变量会通过return语句返回给调用者,所以从内存操作来看,它是一个赋值操作。

std::string msg(){
    std::string input;
    std::cin>>input;
    return input;
}

int main(void){
    auto a=msg();
    std::string b=msg();//msg返回的input复制到了b中
    return 0;
}

遗憾的是C++只支持单返回值,也就是一个函数调用最多只能返回一个值,假如有多个值就只能以形参形式返回了,这种方式对于函数调用就不是很友好,所以C++提出了新的解决思路。

随着业务的复杂度再次增加,函数形参个数可能会增加,或者可能需要返回多个值,然后在多个不同的函数间传递。这样会导致数据容易错乱,并且增加使用者的学习成本。
为了解决这些问题,工程师们提出了面向对象——多个数据打包的技术。表现在语言层面上,就是用类把一组操作和完成这组操作需要的数据打包在一起。数据作为类的属性,操作作为类的方法,使用者通过方法操作内部数据,数据不再需要使用者自己传递,管理。这对于开发者无疑是大大简化了操作。我们称之为面向对象编程,而在函数间传递数据的方式称为面向过程编程。这两种方式底层逻辑其实是一致的,该传递的参数和函数调用一样都不少,但是面向对象的区别是这些繁琐、容易出错的工作交给编译器来做,开发者只需要按照面向对象的规则做好设计工作就好了,剩下的交给编译器。至此,我们的类型系统又向上提升了一级。类不仅是多个类型的聚合体,还是多个函数的聚合体,是比函数更高级的抽象。
可以看下面面向过程编程和面向对象编程的代码对比

struct Computer{
    bool booted;
    friend std::ostream& operator<<(std::ostream& os,const Computer & c){
        os<<"Computing";
        return os;
    }
};

void boot(Computer& c){
    c.booted=true;
    std::cout<<"Booting...";
}

void compute(const Computer& c){
    if(c.booted){
       std::cout<<"Compute with "<<c;
    }
}

void shutdown(Computer& c){
    c.booted=false;
    std::cout<<"Shutdown...";
}

int main(void){
    auto c=Computer();
    boot(c);
    compute(c);
    shutdown(c);
    return 0;                                                                                                         
}

面向过程最主要的表现就是,开发者需要在函数间传递数据,并维护数据状态,上面例子中的数据是c

struct Computer{
    bool booted;
    
    friend std::ostream& operator<<(std::ostream& os,const Computer & c){
        os<<"Computing";
        return os;
    }

    void boot(){
        booted=true;
        std::cout<<"Booting...";
    }

    void compute(){
        if(booted){
            std::cout<<"Compute with "<<this;
        }
    }

    void shutdown(){
        booted=false;
        std::cout<<"Shutdown...";
    }
};

int main(void){
    auto c=Computer();
    c.boot();
    c.compute();
    c.shutdown();
    return 0;
}

可以看出面向对象的代码最主要的变化是,方法的参数变少了,但是可以在方法里面直接访问到类定义的数据。另一个变化发生在调用端。调用端是用数据调用方法,而不是往方法里面传递数据。这也是面向对象的本质——以数据为中心。
当然,类的封装功能只是类功能的一小部分,后面我们会涉及到更多的类知识。作为初学者,我们了解到这一步就能读懂大部分代码了。

总结

类型系统是一门语言的基本构成部分,它支撑着整个系统的高级功能,很多高级特性都是在类型系统的基础上演化而来的。所以学习语言的类型系统有个从低到高,又从高到低的过程,从最基础的类型开始,学习如何从低级类型构筑出高级

首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇驱动开发:内核遍历文件或目录 下一篇C++面试八股文:什么是RAII?

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目