3

c++语法拾遗,一些细节与特性 - _comet

 2 years ago
source link: https://www.cnblogs.com/komet/p/16089478.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

写了2年多的C+STL的acmer,在学习《C++ primer》时总结的一些少见的语法特性与细节。总体还是和题目说的一样这是一篇 c++ 拾遗。

1 变量和基本类型

1.1 基本类型

1.1.1 字面常量

0123 表示的不是带有前导0的数字123,而是代表8进制数字123。

1.2 常量

1.2.1 constexpr

constexpr变量能自动判别赋值的表达式是否是常量表达式,若不是则会报错。

constexpr int mf=20;        //20是常量表达式
constexpr int limit=mf+1;   //mf+1是常量表达式
constexpr int sz=size();    //当size是一个constexpr 函数的时候是是常量表达式
int a=20;
constexpr int b=a+1;        //报错

1.2 处理类型

1.2.1 decltype

decltype能和auto类似的自动判别类型。

decltype(f()) a=b;  //这里的a的类型是f()返回值的类型

编译器并不实际调用函数f,只是将返回值作为a的类型。
实际上decltype(exp) exp是一个表达式所以可以这么写

int a = 0;
decltype(a) b = 1;          //b 被推导成了 int
decltype(10.8) x = 5.5;     //x 被推导成了 double
decltype(x + 100) y;        //y 被推导成了 double
  • 如果 exp 是一个不被括号( )包围的表达式,或者是一个类成员访问表达式,或者是一个单独的变量,那么 decltype(exp) 的类型就和 exp 一致。
  • 如果 exp 是函数调用,那么 decltype(exp) 的类型就和函数返回值的类型一致。
  • 如果 exp 是一个左值,或者被括号( )包围,那么 decltype(exp) 的类型就是 exp 的引用。

2.1 基础

2.1.1 左值

简单的一句话说,左值就是内存。

2.1.2 右值

简单的一句话说,右值就是数据。

2.1.3 求值顺序

c++ 中没有定义表达式的求值顺序,如:

int cnt=0;
int f(){
    return ++cnt;
}
int main(){
    int a=f()-f();      //a的结果可能会因为编译器不同而不同
                        //因为两个f函数的调用顺序没有定义
    return 0;
}
int a=1;
int b=a+a++;            //同样,i的自增和i的调用顺序没定义
int c=a++ + ++a;        //同样无定义
int *d= new int[2];
*d=f(*d++);                 //无定义

另外java是有定义表达式的求值顺序的,从左到右。c++不定义是为了编译器启用更多优化。

3.1 可变参数的函数

3.1.1 initializer_list 形参

int sum(initializer_list<int> li){
    int s=0;
    for(auto i : li)
        s+=i;
    return s;
}

// 调用方法 
sum({1,2,3,4,5});
sum({1,1,1,1});

initializer_lsit 对象中的元素永远是常量类型。
可使用begin、end、size方法进行操作。

3.1.2 省略符形参

void f(...);
void f(cnt,...);

不建议使用,仅为方便c++访问某些c代码设置。

3.2 函数返回

3.2.1 值如何被返回的

值返回的时候回调用拷贝构造函数,创建一个临时对象。

3.2.2 列表返回值

C++11 新标准规定,函数可以返回花括号包围的值的列表。

vector<int> f(){
    return {1,2,3,4};
}

实际上就是花括号赋值,因为值返回的时候回调用拷贝构造函数,如果拷贝构造函数支持花括号,那就可以作为返回值。

3.2.3 返回数组指针

int (*f)()[size];   //表示函数f返回长度为size的数组指针
                    //特别的是这里数组表示是后置的

3.2.4 尾值返回类型

在C++11标准中添加了一种尾置返回类型的定义。

auto f()->int;          //同等于 int f();
auto f()->int *[size];  //同等于 int (*f)()[size]; 

3.3 重载

3.3.1重载与const 形参

顶层的const无法和另一个没有顶层的const的参数区分,但底层的const可以区分

void f(int);
void f(const int);      //重复声明,报错

void f(int *)
void f(int *const)      //重复声明

void f1(int*);
void f1(const int*);    //底层const,新函数。

void f1(int&);
void f1(const int&);    //底层const,新函数。

3.3.2 重载与作用域

当在作用域内重复定义标识符,作用域内的标识符将会直接覆盖外部的标识符,包括重载的。

void f(int);        //函数1
void f(char);       //函数2
void f(double);     //函数3
void foo(){
    void f(double); //函数4
    f(1.2);         //正确调用函数4
    f(1);           //正确调用函数4,传入值为1.0
    f('c')          //错误,没定义
}

3.4 特殊用途语言特性

3.4.1 默认实参

可给函数的参数设置默认实参,但缺省的调用时,会默认赋值。

void f(int a,int b,int c=1,int d=2,int e=3);    //声明

//调用
f(1,2);         //实际为f(1,2,1,2,3);
f(1,2,3);       //实际为f(1,2,3,2,3);
f(1,2,3,4,5);   //实际为f(1,2,3,4,5);

设置默认实参只能从右到左,并且不能留空。

void f(int a=1,int b=2);        //正确
void f(int a=1,int b);          //错误
void f(int a,int b=1,int c);    //错误

默认值可赋函数、变量或者表达式。

int x=1;
int v();
void f(int a=x,int b=v());
// 将在调用f时,调用v,为函数参数赋值

3.4.2 内联函数 inline

在函数的返回类型前面加上关键字inline,就可以将函数声明为内联函数。内联函数有点类似c的宏函数,它会在每一个调用的位置,内联的展开,从而节省函数调用和返回的开销。但内联函数声明只是向编译器发出的请求,编译器可以选择忽略。

inline void f();

内联函数定义通常放在头文件中

3.4.3 constexpr 函数

该函数相当于一个常量表达式。

constexpr int f();

constexpr 函数定义通常放在头文件中

3.5 调试帮助

3.5.1 assert 预处理宏

assert(expr);

当expr为假时输出信息并终止程序。

3.5.2 NDEBUG 预处理变量

当定义NDEBUG后,表示关闭调试。
自定义调试

#ifndef NDEBUG
    //调试代码
#endif

对调试有帮助的标识符

  • __func__ 当前函数名 字符串
  • __FILE__ 文件名 字符串
  • __LINE__ 当前行号 整型
  • __TIME__ 编译时间 字符串
  • __DATE__ 编译日期 字符串

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK