65.9K
CodeProject 正在变化。 阅读更多。
Home

Java、C# 和 C++ 中的 Const 和 ReadOnly

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (5投票s)

2017年1月13日

CPOL

7分钟阅读

viewsIcon

27645

作为我比较系列文章的延续,今天我将比较这三种主要的面向对象语言如何实现 const 和只读变量。

引言

我一直在尝试总结 C#、Java 和 C++ 在处理相似功能时的差异,以帮助我在不同项目之间切换语言。我经常支持或编写这三种语言的新应用程序,每次切换都需要大约一周的时间来回忆起所有必要的细微差别和注意事项才能让我的代码正常工作!

 

所以今天这篇文章是关于 const 和只读,以及一点点 final。

背景

在编写代码时,我们有时希望某些东西是不可变的,这意味着它们在程序运行时不应该改变。对于某些事物,这反映了现实生活,例如代表正方形边数的数值。它是一个真理,因此不是暂时的或可变的,它是一个常量值;如果在代码运行时改变了,我们可能就不再得到应用程序或程序中的正方形了。我们表示的其他事物是代码中的事物,我们只是想确保它们在程序开始和结束时不会改变。我可能想在堆上创建类的 5 个实例,因此在程序中的某个点,我可能想删除该内存,为了确保我不会出现内存泄漏,我希望该值保持不变。

 

Const 函数

在 C++ 中,函数可以标记为 const。这意味着 this 指针(指向当前函数所在实例的指针)不允许更改任何内容。const 函数只能调用其他 const 函数,因此它是编写像 GetNumberOfDogs 这样的访问器函数的好方法。让我们看一下语法。

int MyClass::GetNumberOfDogs() const {}

因此,const 关键字跟在函数签名其余部分的后面。

Java 不支持 const 函数,因此 C# 也不支持 const 函数,这是一个简单的比较!

 

Const 变量

这篇文章的真正内容是变量的比较,哦,这里名字很有讽刺意味,在我们今天的情况下,我们根本不希望它们改变!

C++ 常量变量(又一个矛盾的说法!)

所以我们先从 C++ 开始。属性或局部变量的 const 性是指使其不可变的特性。在整个程序中,我们无法更改其值。语法很简单,将 const 关键字放在类型之前或之后。

int const NumberOfOtherSides = 4;
const int NumberOfSides = 4;
MyClass const myConstInstance;
const MyClass myConstInstance;

const 关键字是给编译器的。因此,在编译时,所有 const 对象都会被检查,以确保 const 函数只调用另一个 const 函数,没有对 const 变量/属性的赋值,没有调用非 const 函数,也没有隐式地将 const 转换为非 const,例如函数参数类型的引用。

请注意,我们可以将 const 对象作为非 const 参数传递给函数,因为它们是副本(我们在 C++ 中按值传递),除非您将引用用作参数类型,在这种情况下,编译器会阻止您隐式转换为非 const 引用,这里有一个概述。

 

class MyClass
{
public:
  void PrintMe() //illegal to call with const instance as the function is not const
  {

  }
  void PrintMeOK() const //const function so it cant change this instance
  {
    //Ok to call me Im const 
  }
};

void okToCall(MyClass copyOfMyInstance) //can call this with const instance as its a copy in the function
{
  //ok to call with const, who cares ? Its either a shallow or deep copy of the instance
}

void Print(MyClass &myPassedInNonReference) const //non const reference argument 
{
  //this could change the instance passed in, as it would be non-const
}

const MyClass myConstInstance;

myConstInstance.PrintMeOK(); //OK as it's a const member function

okToCall(myConstInstance); //This will be a copy that gets into the function, so its ok

myConstInstance.PrintMe(); //illegal trying to call a non-const function with a const instance

Print(myConstInstance); //illegal, trying to implicitly cast from const instance reference to non-const

很好,但是那些棘手的 C++ 人,他们很快就给了我们“越狱卡”,当他们意识到,如果您(我强调“如果”)确实想更改一个 const 变量,那么应该有一种紧急方法可以做到。因此有了 const_cast。您可能正在准备产品发布,并在最后一刻需要调用一个函数,您知道该函数不会更改此实例中的任何内容,但它不是 const 的。您不想取消实例的 const 性,也不想剥离函数及其调用的函数等的 const 性。

我想这就是 C++ 成功的原因,它允许您在最后一刻更改事物,以便您能按时完成产品。当然,这是一把双刃剑,因为这种哲学也允许您滥用它,从第一天开始编写糟糕的代码(如果您不知道自己在做什么的话)。

const_cast 是一种剥离变量 const 性质的方法,它的样子如下。

nonConstVar = const_cast<type>(OriginalConstVar);

对于内置类型,我们不能使用 const_cast。所以例如。

const int myConst = 99;
int myNonConst =  const_cast<int> (myConst);

++myConst;
++myNonConst;

编译器会抱怨不仅尝试使用前缀自增运算符更改 const,而且还抱怨不能将 const_cast 用于 int。

我们不能在复制的同时剥离实例的 const 性,所以这也不会起作用。

MyClass const myInstance;
MyClass nonConstInstance = const_cast<MyClass>(myInstance);

但是我们可以通过指针或引用来剥离 const 性。

MyClass const myInstance;
MyClass &nonConstInstance2 = myInstance;
MyClass &nonConstInstance = const_cast<MyClass&>(myInstance);
    

使用 C++,您可以允许某个对象是可变的。这意味着即使该类的实例被声明为 const,您仍然可以更改用上述关键字标记的部分。

class MyClass
{
  public:
    mutable int NumberOfSides; //Any const instance of this class can still have this variable changed

};

现在是很难解释的部分。在 C++ 中,您可以有一个指针,它本身可以是 const 的,也就是说,它将始终保存相同的地址;或者它可以保存一个 const 变量的地址。或者它可以是 **BOTH**!

const MyClass* myInstance; //We are pointing at a constant instance as far as this pointer goes

MyClass *const myInstance; //The pointer can not be changed to point to another address

const MyClass* const myInstance; //We cant do either

这是一个无法修改常量指针的例子。

class MyClass
{
private:
    int NumberOfSides;
public:
    MyClass(int d)
    {
        NumberOfSides = d;
    }
    void Print() const
    {
        cout<<"Number of sides"<<NumberOfSides<<endl;
    }
};

int main()
{
    MyClass* myar[2]; //Create an array of pointers to my class
    myar[0] = new MyClass(1);
    myar[1] = new MyClass(2);

    MyClass* indxor = myar[0]; // normal non const pointer
    indxor->Print();
    indxor = myar[1];
    indxor->Print();

    const MyClass* indxorConst = myar[0]; //pointer to an instance in my array
    indxorConst->Print();
    indxorConst = myar[1]; //Can point to the next one in the array, as pointer is not const
    indxor->Print();

    MyClass* const constIndxor = myar[0];
    constIndxor = myar[1]; //Can not do this, the pointer is fixed at one address (const)
    constIndxor->Print();
}

您猜对了,我们可以将它们全部组合起来。

const MyClass* const MyHellOfAConstFunction(const char* const Name) const //5 mad consts

{

}

Ok that's enough c++, I'm feeling dizzy.

 

C# Const 变量(又一个矛盾的说法!)

在 C# 中,常量变量根本就不是。就这样。开玩笑。我们只能在声明类常量属性时设置其值。所以我们可以这样做。

public const int c1 = 5;

就这么定了,程序整个生命周期都是如此。您不能拥有此的静态版本,因为运行时只生成一个版本供所有实例共享(为什么每个实例都需要一个?)。另外,您只能拥有值类型的 const,因此不能有引用类型(如类)的常量实例,字符串除外(因为它保证是不可变的)。必须这样,因为没有 const 函数,运行时如何阻止您调用会更改实例的函数?它不知道哪些函数可以更改,哪些不能,因此您的常量实例可能会被更改。

如果您想要一个常量类,您必须设计一个常量类,只需不提供任何允许可变性的函数,例如访问器中的 set 函数,或其他更改任何属性的函数。

如果您想要比编译时静态绑定更灵活的功能,请改用 **readonly** 关键字。它允许您在类完成实例化之前设置值,换句话说,您可以在声明时 **或** 在构造函数期间更改它,因此您可以根据调用了哪个构造函数来拥有不同的值。这确实允许对属性进行有限的上下文设置。

 

Java Const 变量(最后一个矛盾的说法,我保证!)

在 Java 中,有些变量违背了它们的名称,不允许自己改变,这些是用 **final** 关键字指定的。

<code>final int NUMBER_OF_SIDES_TRIANGLE = 3;
NUMBER_OF_SIDES_TRIANGLE = 5;</code>

所以我们在这里会得到一个编译错误,因为我们正在尝试为 final 赋值。

C# 和 Java 的区别在于,如果需要,您必须将变量声明为 static,这在 C# 中是被禁止的。

如果您将 **final** 关键字与对象实例一起使用,那么您就是在说您希望该引用永远指向该实例。

如果您注意到 Java 中的 **const** 关键字是保留关键字,那么恭喜您。但别担心,它根本没有被使用。

就这样。感觉这篇文章 C++ 的内容有点多,但它在一个地方澄清了您可以做什么和不能做什么。

谢谢。

Marcus。

© . All rights reserved.