目录
1.1 const obj 如果调用 non-const member fun会编译出错
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) 它在每个翻译单元拥有相同地址。