const与#define的优缺点

2024-09-06 08:18
文章标签 const 优缺点 define

本文主要是介绍const与#define的优缺点,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.define由预处理程序处理,const由编译程序处理

2.#define不分内存,因为它是预编译指令,编译前进行了宏替换。const 不一定?某种说法,Const常量是占有内存的被冻结了的变量

3.const定义常量是有数据类型的,这样const定义的常量编译器可以对其进行数据静态类型安全检查,而#define宏定义的常量却只是进行简单的字符替换,没有类型安全检查,且有时还会产生边际效应

4.有些调试程序可对const进行调试,但不对#define进行调试。

5.const在编译期间会计算其值,define不会

6 当定义局部变量时,const作用域仅限于定义局部变量的函数体内。但用#define时其作用域不仅限于定义局部变量的函数体内,而是从定义点到整个程序的结束点。但也可以用#undef取消其定义从而限定其作用域范围。只用const定义常量,并不能起到其强大的作用。const还可修饰函数形式参数、返回值和类的成员函数等。从而提高函数的健壮性。因为const修饰的东西能受到c/c++的静态类型安全检查机制的强制保护,防止意外的修改。

 

二、constc++

      看了传递方式后我们继续来谈“const只能用于修饰输入参数的情况。

         当输入参数用值传递方式时,我们不需要加const修饰,因为用值传递时,函数将自动用实际参数的拷贝初始化形式参数,当在函数体内改变形式参数时,改变的也只是栈上的拷贝而不是实际参数。
         
但要注意的是,当输入参数为ADT/UDT(用户自定义类型和抽象数据类型)时,应该将值传递改为“const &传递,目的可以提高效率。
         
例如:
             voidfun(A a);//
效率底。函数体内产生A类型的临时对象用于复制参数 a,但是临时对象的
                          //
构造、复制、析构过程都将消耗时间。
             voidfun(A const &a);//
提高效率。用引用传递不需要产生临时对象,省了临时对象的
                                 //
构造、复制、析构过程消耗的时间。但光用引用有可能改变a,所以加const


         
当输入参数用指针传递方式时,加const修饰可防止意外修改指针指向的内存单元,起到保护作用。
         
例如:
             voidfunstrcopy(char *strdest,const char *strsrc)//
任何改变strsrc指向的内存单元,
                                                             //
编译器都将报错
            
些时保护了指针的内存单元,也可以保护指针本身,防止其地址改变。
         
例如:
            voidfunstrcopy(char *strdest,const char *const strsrc)

(3)const修饰函数的返回值

     如给指针传递的函数返回值加const,则返回值不能被直接修改,且该返回值只能被赋值给加const修饰的同类型指针。
     
例如:
         const char *GetChar(void){};
      
赋值 char *ch = GetChar();//错误     const char *ch = GetChar();//正确

(4)const修饰类的成员函数(函数定义体)

     任何不会修改数据成员的函数都应用const修饰,这样当不小心修改了数据成员或调用了非const成员函数时,编译器都会报错。
      const
修饰类的成员函数形式为:int GetCount(void)   const;
5)用传引用给const取代传值
缺省情况下,C++ 以传值方式将对象传入或传出函数(这是一个从 C 继承来的特性)。除非你特别指定其它方式,否则函数的参数就会以实际参数(actual argument)的拷贝进行初始化,而函数的调用者会收到函数返回值的一个拷贝。这个拷贝由对象的拷贝构造函数生成。这就使得传值(pass-by-value)成为一个代价不菲的操作。例如,考虑下面这个类层级结构:

class Person {
public:
Person(); // parametersomitted for simplicity
virtual ~Person(); // seeItem 7 for why this is virtual
...

private:
std::string name;
std::string address;
};

class Student: publicPerson {
public:
Student(); // parametersagain omitted
~Student();
...

private:
std::string schoolName;
std::stringschoolAddress;
};

  现在,考虑以下代码,在此我们调用一个函数—— validateStudent,它得到一个 Student 参数(以传值的方式),并返回它是否验证有效的结果:

boolvalidateStudent(Student s); // function taking a Student
// by value

Student plato; // Platostudied under Socrates

bool platoIsOK =validateStudent(plato); // call the function

  当这个函数被调用时会发生什么呢?

  很明显,Student 的拷贝构造函数被调用,用 plato 来初始化参数 s。同样明显的是,当 validateStudent 返回时,s 就会被销毁。所以这个函数的参数传递代价是一次 Student 的拷贝构造函数的调用和一次 Student 的析构函数的调用。

  但这还不是全部。一个 Student 对象内部包含两个 string 对象,所以每次你构造一个 Student 对象的时候,你也必须构造两个 string 对象。一个 Student 对象还要从一个 Person 对象继承,所以每次你构造一个 Student 对象的时候,你也必须构造一个 Person 对象。一个 Person 对象内部又包含两个额外的 string 对象,所以每个 Person 的构造也承担着另外两个 string 的构造。最终,以传值方式传递一个 Student 对象的后果就是引起一次 Student 的拷贝构造函数的调用,一次 Person 的拷贝构造函数的调用,以及四次 string 的拷贝构造函数调用。当 Student 对象的拷贝被销毁时,每一个构造函数的调用都对应一个析构函数的调用,所以以传值方式传递一个 Student 的全部代价是六个构造函数和六个析构函数!

  好了,这是正确的和值得的行为。毕竟,你希望你的全部对象都得到可靠的初始化和销毁。尽管如此,如果有一种办法可以绕过所有这些构造和析构过程,应该变得更好,这就是:传引用给 constpass by reference-to-const):

boolvalidateStudent(const Student& s);

  这样做非常有效:没有任何构造函数和析构函数被调用,因为没有新的对象被构造。被修改的参数声明中的 const 是非常重要的。 validateStudent 的最初版本接受一个 Student 值参数,所以调用者知道它们屏蔽了函数对它们传入的 Student 的任何可能的改变;validateStudent 也只能改变它的一个拷贝。现在 Student 以引用方式传递,同时将它声明为 const 是必要的,否则调用者必然担心 validateStudent 改变了它们传入的 Student

  以传引用方式传递参数还可以避免切断问题(slicing problem)。当一个派生类对象作为一个基类对象被传递(传值方式),基类的拷贝构造函数被调用,而那些使得对象的行为像一个派生类对象的特殊特性被切断了。你只剩下一个纯粹的基类对象——这没什么可吃惊的,因为是一个基类的构造函数创建了它。这几乎绝不是你希望的。例如,假设你在一组实现一个图形窗口系统的类上工作:

class Window {
public:
...
std::string name() const;// return name of window
virtual void display()const; // draw window and contents
};

class WindowWithScrollBars:public Window {
public:
...
virtual void display()const;
};

  所有 Window 对象都有一个名字,你能通过 name 函数得到它,而且所有的窗口都可以显示,你可一个通过调用 display 函数来做到这一点。display virtual 的事实清楚地告诉你:一个纯粹的基类的 Window 对象的显示方法有可能不同于专门的 WindowWithScrollBars 对象的显示方法。

  现在,假设你想写一个函数打印出一个窗口的名字,并随后显示这个窗口。以下这个函数的写法是错误的:

voidprintNameAndDisplay(Window w) // incorrect! parameter
{
 
// may be sliced!
std::cout <<w.name();
w.display();
}

  考虑当你用一个 WindowWithScrollBars 对象调用这个函数时会发生什么:

WindowWithScrollBarswwsb;

printNameAndDisplay(wwsb);

  参数 w 将被作为一个 Window 对象构造——它是被传值的,记得吗?而且使 wwsb 表现得像一个WindowWithScrollBars 对象的特殊信息都被切断了。在 printNameAndDisplay 中,全然不顾传递给函数的那个对象的类型,w 将始终表现得像一个 Window 类的对象(因为它就是一个 Window 类的对象)。特别是,在 printNameAndDisplay 中调用 display 将总是调用 Window::display,绝不会是 WindowWithScrollBars::display

  绕过切断问题的方法就是以传引用给 const 的方式传递 w

voidprintNameAndDisplay(const Window& w) // fine, parameter won’t
{
// be sliced
std::cout <<w.name();
w.display();
}

  现在 w 将表现得像实际传入的那种窗口。

  如果你掀开编译器的盖头偷看一下,你会发现用指针实现引用是非常典型的做法,所以以引用传递某物实际上通常意味着传递一个指针。由此可以得出结论,如果你有一个内建类型的对象(例如,一个 int),以传值方式传递它常常比传引用方式更高效。那么,对于内建类型,当你需要在传值和传引用给 const 之间做一个选择时,没有道理不选择传值。同样的建议也适用于 STL 中的迭代器(iterators)和函数对象(function objects),因为,作为惯例,它们就是为传值设计的。迭代器(iterators)和函数对象(function objects)的实现有责任保证拷贝的高效并且不受切断问题的影响。(这是一个规则如何变化,依赖于你使用 C++ 的哪一个部分的实例。)

  内建类型很小,所以有人就断定所有的小类型都是传值的上等候选者,即使它们是用户定义的。这样的推论是不可靠的。仅仅因为一个对象小,并不意味着调用它的拷贝构造函数就是廉价的。很多对象——大多数 STL 容器也在其中——容纳的和指针一样,但是拷贝这样的对象必须同时拷贝它们指向的每一样东西。那可能是非常昂贵的。

  即使当一个小对象有一个廉价的拷贝构造函数,也会存在性能问题。一些编译器对内建类型和用户定义类型并不一视同仁,即使他们有同样的底层表示。例如,一些编译器拒绝将仅由一个 double 组成的对象放入一个寄存器中,即使在常规上它们非常愿意将一个纯粹的 double 放入那里。如果发生了这种事情,你以传引用方式传递这样的对象更好一些,因为编译器理所当然会将一个指针(引用的实现)放入寄存器。

  小的用户定义类型不一定是传值的上等候选者的另一个原因是:作为用户定义类型,它的大小常常变化。一个现在较小的类型在将来版本中可能变得更大,因为它的内部实现可能会变化。甚至当你换了一个不同的 C++ 实现时,事情都可能会变化。例如,就在我这样写的时候,一些标准库的 string 类型的实现的大小就是另外一些实现的七倍。

  通常情况下,你能合理地假设传值廉价的类型仅有内建类型及 STL 中的迭代器和函数对象类型。对其他任何类型,请遵循本 Item 的建议,并用传引用给 const 取代传值。

Things to Remember

·用传引用给 const 取代传值。典型情况下它更高效而且可以避免切断问题。

·这条规则并不适用于内建类型及 STL 中的迭代器和函数对象类型。对于它们,传值通常更合适。

 

原文来源:http://dev.firnow.com/course/3_program/c++/cppjs/20091014/178760.html

 

这篇关于const与#define的优缺点的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1141484

相关文章

c++的初始化列表与const成员

初始化列表与const成员 const成员 使用const修饰的类、结构、联合的成员变量,在类对象创建完成前一定要初始化。 不能在构造函数中初始化const成员,因为执行构造函数时,类对象已经创建完成,只有类对象创建完成才能调用成员函数,构造函数虽然特殊但也是成员函数。 在定义const成员时进行初始化,该语法只有在C11语法标准下才支持。 初始化列表 在构造函数小括号后面,主要用于给

脏页标记技术的优缺点详解

脏页标记技术的优缺点 一、引言 在数据库系统中,脏页标记技术是一种用于管理数据页修改状态的重要机制。它能够帮助数据库系统有效地跟踪哪些数据页被修改过,以便在适当的时候将这些脏页写入磁盘,保证数据的一致性和持久性。然而,脏页标记技术也并非完美无缺,它既有优点也有一些潜在的缺点。 二、脏页标记技术的优点 (一)提高数据一致性 确保数据完整性 脏页标记使得数据库系统能够清楚地知道哪些数据页已经

多款式随身WiFi如何挑选,USB随身WiFi、无线电池随身WiFi、充电宝随身WiFi哪个好?优缺点分析!

市面上的随身WiFi款式多样琳琅满目,最具代表性的就是USB插电款、无线款和充电宝款。今天就来用一篇文章分析一下这三种款式的优缺点。 USB插电款 优点:便宜,无需充电,在有电源的地方可以随时随地插电使用,比如中兴的USB随身WiFi。 缺点:无电源的情况下,无法带出门使用,部分品牌考虑到这个问题,会配备一个充电仓,这个充电仓相对来说就有点累赘了。网速上也不太稳定,波动比较大。

PowerBI DAX中计算列和度量值之间有什么优缺点?

在Power BI中,度量值(Measures)和新建列(Calculated Columns)都是使用DAX(数据分析表达式)来创建的,它们都可以用来进行数据计算和分析。但是,它们在使用场景、性能和交互性方面有所不同。 以下是度量值和新建列的优缺点: 度量值(Measures) 优点: 性能优化:度量值在内存中进行计算,通常比基于行的计算(如新建列)更快。参与视觉对象

s let 和const的区别 ,它们可以变量提升吗

在 JavaScript 中,let 和 const 是 ES6 引入的新变量声明关键字,它们与之前的 var 关键字相比,有几个重要的区别。特别是关于变量提升(hoisting)的行为不同。 变量提升(Hoisting) 在 JavaScript 中,“变量提升”是指变量声明会被提升到作用域的顶部,但变量的初始化不会。这意味着你可以先使用变量,然后再声明它。然而,let 和 const 的行

2、#include和#define

#include和#define 一、#include二、#define宏定义1、宏变量2、宏函数 一、#include #include是预处理指令,会将头文件直接替换到文件中。 // hello.hvoid foo() {int c = 0;int d = 1;}// hello.cpp#include "hello.h"int main() {int a =

全面解析时间序列算法:原理、应用场景与优缺点

时间序列分析是预测分析中一个重要领域,广泛应用于金融市场、经济预测、物联网数据、库存管理等多个领域。随着时间序列数据的复杂性增加,单纯依靠传统统计方法已经难以满足实际需求。本文将从传统统计方法、机器学习方法到深度学习方法,对时间序列分析的主流算法进行全面解析,深入探讨其原理、适用场景、优缺点,帮助大家更好地理解和选择合适的算法。 一、传统统计方法 1. 自回归模型(AR, Autoregr

C++中const关键字的使用方法,烦透了一遍一遍的搜,总结一下,加深印象!!!

之前一直在学习C/C++,关于const的使用,这里出现一点,那里出现一点。知识用时方恨少,这一段时间正好各种笔试题,其中关于const的用法也是层出不穷,所以疲于在书本上各种翻,这里汇总一下,加深自己的印象的同时,也方便以后查阅和学习。菜鸟一个,若有错误,望指正! const关键字 常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。不管出现在任何上

磁吸轨道灯的优缺点深度解析:为你的家居照明提供新选择

在现代家居装修中,照明设计已成为提升居住品质的重要一环。磁吸轨道灯作为一种新兴的照明解决方案,以其独特的灵活性和美观性逐渐受到市场的青睐。然而,任何产品都有其两面性,磁吸轨道灯也不例外。本文将深入探讨磁吸轨道灯的优缺点,并为你种草与无主灯设计相关的灵感,同时确保内容遵循各大平台的规范,避免任何违规营销行为。 磁吸轨道灯的优点 1. 高度灵活性 磁吸轨道灯的最大亮点在于其高度的灵活性。用户可

【深度学习】LSTM模型,GRU模型计算公式及其优缺点介绍

一.LSTM介绍 LSTM(Long Short-Term Memory)也称长短时记忆结构, 它是传统RNN的变体, 与经典RNN相比能够有效捕捉长序列之间的语义关联, 缓解梯度消失或爆炸现象. 同时LSTM的结构更复杂, 它的核心结构可以分为四个部分去解析: 遗忘门输入门细胞状态输出门  1LSTM的内部结构图  1.1 LSTM结构分析 结构解释图:   遗忘门部分结构图与计算