C++声明

本文详细介绍了C++中的const关键字在对象和成员函数中的应用,包括const对象调用非const成员函数会出错的经典错误,以及STL中const在操作符重载中的运用。此外,还探讨了内联函数的原理,包括为何引入内联、使用限制、inline的性质,以及内联函数的定义位置。最后,强调了const和inline在C++中的重要性和规则。

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

目录

1.const

1.1 const obj 如果调用 non-const member fun会编译出错

经典错误

1.2 例子:STD里的操作符重载

1.3 例子:《cpp primer》15节 -基类的定义


1.const

class complex
{
public:
  complex (double r = 0, double i = 0): re (r), im (i) { }
  complex& operator += (const complex&);
  complex& operator -= (const complex&);
  complex& operator *= (const complex&);
  complex& operator /= (const complex&);
  double real () const { return re; }
  double imag () const { return im; }
private:
  double re, im;

  friend complex& __doapl (complex *, const complex&);
  friend complex& __doami (complex *, const complex&);
  friend complex& __doaml (complex *, const complex&);
};

只有member fun后面可以用 const定义。成员变量后面不可以写const,写在前面。


1.1 const obj 如果调用 non-const member fun会编译出错

经典错误

如果定义时没有写成void print() const{}; 就会报错。因为str是const-obj,不能用non-const member fun调用。 


1.2 例子:STD里的操作符重载

来自 TEMPLATE CLASS basic_string的定义。string类的operator[]。

  • 用户可能拿[]来改变字符串。如String str[5]="a";由于string的实现机制是共享模式,不可以直接改变内部内容。只能拷贝一份再改变。所以要做cow(copy on write)设计。
  • 如果const String str[2];str是const obj,其内部不可以动。所以不必考虑cow。const obj只会调用const fun,所以const fun不必写cow。
reference operator[](size_type _Off)
{
    // 下标可变序列
    // 必须考虑cow
  
    return (this->_Myptr()[_Off]);
}

const_reference operator[](size_type _Off) const
{
	// 下标不可变序列
    // 不必考虑cow

    return (this->_Myptr()[_Off]);
}
  • 可见const属于签名的一部分,这两个函数可以重载。 
  • C++规定:如果类中有成员函数的const fun 和nonconst fun同时存在,则const obj默认调用const fun,non-const obj默认调用non-const fun。
  • C++规定:non-const fun(obj可变)可以调用const fun(obj不可变)。 反过来报错。

1.3 例子:《cpp primer》15节 -基类的定义

class Quote {
	/* 
	** istream&:必须是非常量,因为本操作就是向流读出数据,其状态会改变;
	** Quote& :必须是非常量,因为本操作就是向此对象写入数据,此对象值会改变;
	*/  
	friend istream& operator>>(istream&, Quote&);
 
	
	/*
	**ostream& :必须是非常量,因为本操作就是向流写入数据,其状态会改变;
	**const Quote& :引用是因为避免赋值实参;常量是因为通常打印对象不会改变对象本身的值;
	*/
	friend ostream& operator<<(ostream&, const Quote&);  

public:

	/*这种传递中的代码使用以下变量来控制编译;
	**变量:IN_CLASS_INITS/DEFAULT_FCNS;对应C++作用:类初始状态设置/default(默认);
	*/
#if defined(IN_CLASS_INITS) && defined(DEFAULT_FCNS)
	//若定义类初始值设置及默认值,则定义默认构造函数,不接受任何实参;
	Quote() = default;  
#else
	/*若未定义类初始值及默认值,则定义构造函数,函数体空;
	**构造函数初始列表为新创建的数据成员price初始化,对应初始化值为 0.0;
	*/
	Quote() : price(0.0) { }
#endif // !defined(IN_CLASS_INITS) && defined(DEFAULT_FCNS)


	//初始化构造函数
	Quote(const string &book,double sales_price):
		bookNo(book),price(sales_price){ }

	//虚析构函数,动态绑定
#ifdef DEFAULT_FCNS
	virtual ~Quote() = default;
#else
	virtual ~Quote() {	}
#endif // DEFAULT_FCNS

	//const fun。 因为bookNo被期望是const obj,所以只能被const fun调用,必须定义为const fun,否则报错。
	string isbn() const 
	{ return bookNo; }

	//虚函数。将在派生类中重写,根据书的数量,采取不同的折扣算法。
	//因为price(定价)也是const obj,不会被改。所以定义为const fun。
	virtual double net_price(size_t n) const 
	{ return n*price; }//虚函数返回动态分配的自身副本


private:
	const string bookNo;//书号,被期望是const obj

protected:

#ifndef IN_CLASS_INITS
	const double price = 0.0;
#else
	const double price;
#endif // !IN_CLASS_INITS
};

 


2.inline 

1.C/C++为了解决频繁调用小函数消耗栈空间的问题,引入内联函数。栈空间就是存放局部数据的内存空间。大量调用函数会造成栈空间不足而程序出错,inline避免了调用函数对栈内存重复开辟所带来的消耗。

#include <string>
using std::string;

#include <iostream>
using std::cout; using std::endl;

//inline version: find the shorter of two strings
inline const string &
shorterString(const string &s1, const string &s2)
{
        return s1.size() <= s2.size() ? s1 : s2;
}

int main()
{
	string s1("successes"), s2("failure");
	cout << shorterString(s1, s2) << endl;

	// call the size member of the string returned by shorterString
	cout << shorterString(s1, s2).size() << endl;

	// equivalent code as generated by the call to inline version
	// of shorterString
	cout << (s1.size() < s2.size() ? s1 : s2) << endl;
	return 0;
}

 

2.inline使用限制:只能是小函数,不能包含复杂的结构控制语句,不能是递归函数。

3.inline只是对编译器的建议,最后是否内联,看编译器的意思,如果不复杂就会在调用点展开,真正内联。并不是声明了内联就会内联。

4.建议inline函数的定义放在头文件中。因为在不同的单元都会调用内联函数,所以放在头文件中最合适。声明和定义要一致,最好定义和声明是一样的,如果不一样,由编译器决定。内联函数最好放在文件头。

// 头文件
#ifndef EXAMPLE_H
#define EXAMPLE_H
// 包含于多个源文件的函数必须为 inline
inline int sum(int a, int b) 
{
    return a + b;
}
#endif
 
// 源文件 #2
#include "example.h"
int a()
{
    return sum(1, 2);
}
 
// 源文件 #1
#include "example.h"
int b()
{
    return sum(3, 4);
}

5.类中、结构体、联合中定义的函数,不管是成员函数还是非成员的友元函数,都为隐式的inline函数,即缺省都是内联的。如果未在类内给出定义,又想内联该函数,在类外要加inline。

直接在类声明中定义成员函数,虽然书写方便,但是不是很好的编程风格。

class A
{
    public:void Foo(int x, int y) {  } // 自动地成为内联函数
}

应该改成:

// 头文件
class A
{
    public:
    void Foo(int x, int y);
}
// 定义文件
inline void A::Foo(int x, int y){} 

6.inline 必须和函数定义体放在一起才能起作用,跟声明放在一起没有作用。所以inline是一种用于实现的关键字,而不是用于声明的关键字。声明和定义不可以混为一谈,用户没有必要知道函数是否需要内联。

7.类的构造函数和析构函数会隐藏一些行为,比如执行了基类或者成员对象的构造函数或析构函数。不要将构造/析构函数的定义体放在声明中,编译器会根据函数的定义体,自动取消不值得的内联,进一步说明了inline不应该在函数的声明中。

8.inline 关键词的本意是作为指示器,以指示函数的内联替换优先于函数调用,即取代转移控制到函数体内的执行函数调用的 CPU 指令,执行函数体的副本而无需生成调用。这会避免函数调用的开销(传递参数及返回结果),但它可能导致更大的可执行文件,因为函数体必须被复制多次。


【c++11】声明有 constexpr 的函数是隐式的 inline 函数。被删除函数是隐式的内联函数:其(被删除的)定义可出现在多于一个翻译单元中。(constexpr - 指定变量或函数的值能出现在常量表达式中。)


1) inline 函数 在程序中可拥有多于一次定义,只要每个定义都出现在不同翻译单元中(对于非 static inline 函数 )且所有定义等同。

例如, inline 函数 可定义于为多个源文件所 #include 的头文件中。

2) inline 函数 的定义必须存在于访问它的翻译单元中(不必在访问点前)。

3) 拥有外部链接(即不声明为 static )的 inline 函数 拥有下列属性:

  • 1) 它必须在每个翻译单元声明为 inline 。
  • 2) 它在每个翻译单元拥有相同地址。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值