zoukankan      html  css  js  c++  java
  • 细节问题(三)

    1. 再论左值与右值

      以前写过关于左值和右值的两篇总结,这里还需要再补充一点。那天在知乎看到Milo大神的这样一个回答:知乎

    自己也去查了文档,cppreference也清楚地指出:赋值操作在C++和C语言中确实是不一样的,在C++中返回左值(而且是引用)。

    所以(a = b) = 1这样的表达式在C语言中是编译错误的,在C++中却可以编译成功,正如Milo大神如是说:因为 C++ 提供了引用及运算元重载,才可以返回更方便的左值。

      C++和C语言中的左值和右值的概念其实并无太大区别,只是C++11中为了引入右值引用,新添加了一个将亡值(expiring value)而已,而C++中的纯右值其实是C

    语言中的右值。如下图:

    左值表示持久,右值表示临时。位于赋值号左侧和右侧从来不是左值和右值的判断标准,能不能以 & 运算符取地址才是判断的唯一标准。

    典型的右值有中间计算量,函数返回的临时对象,字面值常量(除字符串)等,这些cppreference都有明确地写到。

      下面再来看这样一段代码:

     1 #include <iostream>
     2 #include <string>
     3 using namespace std;
     4 
     5 int main()
     6 {
     7     string s1 = "Hello";
     8     string s2 = "world";
     9 
    10     cout << (s1 + s2 = "OK") << endl;
    11 
    12     int i = 0; 
    13     int j = 20;
    14 
    15     //cout << (i + j = 30) << endl;       //编译不通过
    16 }

    在以前那篇随笔中我也记得在primer中看到过,可以对 s1 + s2这样的右值赋值,当时觉得就是为了兼容旧标准也没多想。但是却不能对 i + j 这样的临时对象赋值。

    其实因为string类重载了赋值运算符,所以 s1 + s2 = "OK"就相当于 (s1 + s2).operator=("OK"),是以一个右值来调用成员函数的行为,C++是允许这样做的,虽然没有

    太大意义,因为 s1 + s2 这个临时变量在分号之后就不存在了。

    C++ 11下规定可以禁止这种行为,就是不允许右值来调用成员函数。

     1 class Foo
     2 {
     3     void callByLvalueOnly() & // & 限定该成员函数只能被左值调用
     4     {
     5     }
     6 
     7     void callByRvalueOnly() &&  // && 限定该成员函数只能被右值调用
     8     {
     9     }
    10 
    11     Foo & operator=(const Foo&) & //  限定了只能对左值进行赋值
    12     {
    13     }
    14 };

    具体内容可以参考知乎下的这个问题:知乎

    2. 再论Java和C++中的static

      以前写过这样的一篇博客:Java和C++中的static,那时候理解的还非常浅。

      Java中的 static 表示的是类成员或者类方法,其中有一种代码块叫静态代码块,静态代码块以及静态成员的初始化是在类加载的时候就会进行的(其实类初始化是类加载的

     最后一步)。

     而Java中有独特的运行时动态加载特点,只有在用到这个类的时候才会进行加载。常见的可以触发类加载的有:调用类的静态方法/静态属性,实例化类的对象等。

       根据Java虚拟机规范,主动触发类加载的行为主要有以下六种:

     1) 创建类的实例。
     2) 访问某个类或者接口的静态变量,或者对该静态变量赋值(如果访问静态编译时常量(即编译时可以确定值的常量)不会导致类的初始化)。
     3) 调用类的静态方法。
     4) 反射(Class.forName(xxx.xxx.xxx))。
     5) 初始化一个类的子类(相当于对父类的主动使用),不过直接通过子类引用父类元素,不会引起子类的初始化。
     6) Java虚拟机被标明为启动类的类(包含main方法的)。

     类与接口的初始化不同,如果一个类被初始化,则其父类或父接口也会被初始化,但如果一个接口初始化,则不会引起其父接口的初始化。参见:Java系列笔记

       

       但是C++不同,C++语言没有运行时的动态类加载机制。C++的类中的静态变量是在运行时就得到内存分配的,存储在全局/静态变量区,而且会被默认初始化。(int 为 0)

       静态变量的存在与类有没有被“加载”没有关系,C++没有动态类加载机制。

    3. Thinking in Java中的一个错误

        《Java编程思想》第96页描述的:即使没有显式地使用static关键字,构造器实际上也是静态方法。这句话是作者的主观认识,是完全错误的。

        构造器是毫无疑问的实例方法,而且是非常特殊的实例方法(没有返回值、无法通过 . 来调用等),构造器的参数列表中会有被隐式传入的this。

        然后构造器作为一种实例方法,是静态分派的(statically dispatched),也就是不是运行时绑定的,无法参与运行时多态。这一点上和private、final修饰的实例方法相同,

        都是实例方法,却都是静态分派的,无法参与运行时多态。事实上,private方法就是隐式final的,这点TIJ上倒是没有说错。

        具体参考知乎R大神的详细答案:知乎

    4. 关于头文件

        轮子哥在知乎说:只要不会引起编译错误,并且不会要求你要使用前置声明的话,那尽量放在cpp里面总是不会错的。只有很少数的情况下你需要考虑要不要放进头文件里,

        譬如说inline函数,或者static const int变量等。

      实际编码设计过程中,一个原则就是在类的头文件中最好不要包含其他头文件,因为这样会使类之间的文件包含关系变得复杂化。

      注意使用前向声明。一定不要在头文件中使用using namespace。

        参考:注意头文件规则

    5. 模板不能实现分离式编译

       C++的模板template不能实现分离式编译,通常都是将模板的声明与实现都放在头文件中,标准库都是这样做的。编译器很聪明,针对模板这种行为,不会引起重定义的错误。

       关于原因,具体参见刘未鹏大神的文章:为什么C++编译器不能支持模板的分离式编译

    6. 编译链接和运行

       编译是读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,主要包括预处理、编译、优化等主要阶段。

       链接过程是由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。 例如,某个源文件中的函数可能引用了另一个源文件

       中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。

       运行就是让程序跑起来,是唯一的存在内存分配和回收的过程。

    7. 关于C++中的命名空间

        命名空间其实也是一种作用域,和类、函数、块甚至文件是一样一样的。

       通常也是在头文件中定义命名空间,然后在cpp文件中实现时需要用作用域操作符 ::来限定哪个命名空间。命名空间的详细说明:MSDN

       当然也可以像在平时写demo一样时在 main.cpp 下定义并实现一个类一样,来定义命名空间。

    8. 析构函数

       以前说的很清楚,C++编译器只有在需要提供默认构造函数的时候,才会提供合成的默认构造函数。而需要的情况就是以前总结出来的五种情况:

       父类有默认构造函数,类的数据成员是别的具有默认构造函数的类实例,虚函数,虚继承,以及类内初始化。

       而对于析构函数,不能有参数不能重载,只有前两种情况会决定会不会合成析构函数:即父类有默认析构函数,类的数据成员是别的有默认析构函数的类实例。后三种

       情况跟析构函数没有半毛钱关系。

       五大函数加上构造函数这六大函数都是不能被继承的。

       运算符重载的函数都是能够被继承的,除了赋值操作符。

       

  • 相关阅读:
    [Zabbix5.0]Transaction check error: file /etc/my.cnf from install of Percona-Server-shared-56-5.6.48-rel88.0.1.el7.x86_64 conflicts with file from package mysql-community-server-5.7.30-1.el7.x86_64
    [CentOS7]Delta RPMs disabled because /usr/bin/applydeltarpm not installed.
    [CentOS7]YUM只下载不安装
    [CentOS7]expect传参
    [CentOS7]查询FCSAN WWN
    [Docker]WARNING: IPv4 forwarding is disabled. Networking will not work.
    [Docker]CentOS8.2安装 Docker-compose
    [CentOS8]安装vmware tools
    [CentOS8]弹出CDROM
    书单
  • 原文地址:https://www.cnblogs.com/niuxichuan/p/5994511.html
Copyright © 2011-2022 走看看