插入汇编语句
asm语句可在C或C++程序中插入汇编代码。
VS2019编译器使用“_asm”插入汇编代码。
static_assert用于提供静态断言服务,即在编译时判定执行条件是否满足。
使用格式为 static_assert(条件表达式, ”输出信息“)。
不满足则编译报错。
C++函数
函数说明不定义函数体,函数定义必须定义函数体。说明可多次,定义仅能实施一次。
函数可说明或定义为四种作用域 :
(1) 全局函数(默认);
(2) 内联即inline函数;
(3) 外部即extern函数;
(4) 静态即static函数
全局函数可被任何程序文件(.cpp)的程序用( 只有全局main函数不可被调用(新标准) ),故它是全局作用域的。
内联函数可在程序文件内或类内说明或定义,只能被当前程序文件的程序调用。它是文件局部文件作用域的,可被编译优化(掉)。
静态函数可在程序文件内或类内说明或定义。类内的静态函数不是文件局部文件作用域的,程序文件内的静态函数是文件局部文件作用域的。
//函数说明可以进行多次,但定义只能在某个程序文件(.cpp)进行一次。
int d() { return 0; } //默认定义全局函数d:有函数体
extern int e(int x); //说明函数e:无函数体。可以先说明再定义,且可以说明多次
extern int e(int x) { return x; } //定义全局函数e:有函数体,其他模块不能定义e()的函数体
inline void f() { } //函数f:有函数体,不优化,内联。仅当前程序文件可调用
void g() { } //全局函数g:有函数体,无优化。
static void h() { } //函数h:有函数体,无优化,静态。仅当前程序文件可调用
void main(void) {extern int d(), e(int); //说明要使用外部函数:d, e均来自于全局函数。可以说明多次。extern void f(), g(); //说明要使用外部函数:f来自于局部函数, g来自于全局函数extern void h(); //说明要使用外部函数:h来自于局部函数
}
省略参数 ...
//省略参数 ... 表示可以接受0至任意个任意类型的参数。通常须提供一个参数表示省略了多少个实参。
long sum(int n, ...) {long s = 0; int* p = &n + 1; //p指向第1个省略参数for (int k = 0; k < n; k++) s += p[k];return s;
}
void main() {int a = 4; long s = sum(3, a, 2, 3); //执行完后s=9
}
//参数n和省略参数连续存放,故可通过& n + 1得到第1个省略参数的地址;若省略参数为double类型,则应进行强制类型转换。
double* p = (double*)(&n + 1);
参数默认值
声明或定义函数时也可定义参数默认值,调用时若未传实参则用默认值。 函数说明或者函数定义只能定义一次默认值。 默认值所用的表达式不能出现同参数表的参数。 所有默认值必须出现在参数表的右边,默认值参数中间不能出现没有默认值的参数。VS2019的实参传递是自右至左的,即先传递最右边的实参.
若同时定义有函数int f(int m, ...); 则调用f(3)可解释为调用int f(int m, ...); 或调用int f(int x, int y = u + 2, int z = 3)均可,故编译会报二义性错误。int u = 3; int f(int x, int y = u + 2, int z = 3) { return x + y + z; } int w = f(3) + f(2, 6) + f(1, 4, 7); //等价于w = f(3,5,3)+f(2,6,3)+f(1,4,7);
函数内联
编译会对内联inline函数调用进行优化,即直接将其函数体插入到调用处,而不是编译为call指令,这样可以减少调用开销,提高程序执行效率。
调用开销是指为完成调用所进行的实参传递、重要寄存器保护及恢复以及返回时的栈指针恢复到调用前的值所额外编译或执行的指令。
若f1、f2的调用开销分别为10、7,函数体指令数分别为5、20,程序对f1和f2均有100个位置调用。则调用f1、f2编译后的指令数:
成功内联f1 = 100 * 5,不内联f1 = 10 * 100 + 5 = 1005: 函数体小,内联合算
成功内联f2 = 100 * 20,不内联f1 = 7 * 100 + 20 = 720 : 函数体大,调用合算
由此可见:函数体相对较小的函数,使用内联更合算。
若函数为虚函数、或包含分支(if, switch, ? : , 循环, 调用),或取函数地址,或调用时未见函数体,则内联失败。
内联失败不代表程序有错,只是被编译为函数调用指令。
constexpr
constexpr 是 C++11中新增的关键字,表示它修饰的对象是常量。
基本的常量表达式:字面值、全局变量 / 函数的地址、sizeof等关键字返回的结果。
constexpr 用来修饰:变量、函数的返回值、构造函数。用 constexpr 定义的变量不能重新赋值(具有const 特性)。
- constexpr 修饰变量:
constexpr变量,必须在定义时赋值,并且编译时计算出其值为常量;
constexpr变量,如果不是用函数调用赋值,则等价于const;
constexpr变量,如果用函数调用赋值,则函数必须是constexpr且在编译时计算出的返回值是常量;
例如:
constexpr int x = expression(常量表达式); 等价:const int x = …
constexpr int y = f(); //f()必须是constexpr且编译时返回值是常量.
Problem: 在任何情况下,可以使用 constexpr 替换 const 吗 ?
-
constexpr 修饰函数的返回值:
在调用时,一般需要将函数返回值赋值给一个constexpr变量,这时表示在编译时就能计算出constexpr函数的返回值,返回值一定是常量(否则报错); 如果调用时,函数返回值没有赋值给一个constexpr变量,则等价于该函数没有constexpr属性。 例如:
constexpr int f(int x) { return x + 1; }
int a = 1;
f(1); f(a); //正确,等价于f()没有constexpr属性
const int x1 = f(1); //正确,等价于f()没有constexpr属性
const int x2 = f(a); //正确,等价于f()没有constexpr属性
constexpr int y1 = f(1); //正确,编译为 constexpr int y1 = 2;
constexpr int y2 = f(a); //错误,编译时不能算出f(a) (因为a不是常量)
constexpr函数内部调用函数时,被调用函数必须是constexpr;
constexpr函数内部不能有goto语句或标号,也不能有try语句块;
constexpr函数内部不能定义或使用static变量、线程本地变量等永久期限变量;
constexpr函数作用域相当于static;
函数main为全局作用域,故不能定义为constexpr函数。
-
constexpr 修饰类的构造函数
constexpr 构造函数必须将所有没有缺省值的成员变量放到初始化列表中,函数体内的内容必须全部是 constexpr 的。
改错练习
#include <iostream>
constexpr int f(int n) //调用此函数时,在编译时就能确定其返回值
{return n + 1;
}
int main()
{constexpr int a = f(1); //对:编译时能计算出 a 的值constexpr int b = a * 2 + 1; //对:编译时能计算出 b 的值constexpr int c = f(std::cin.get()); //错:编译时不能计算出 c 的值int d = f(std::cin.get()); //对:不需要编译时计算出 d 的值,运行时计算d的值int k = 5;int x1[k]; //错:数组维数不能为常规的变量(维数必须在编译时就能确定)int x2[b]; //对:编译为 x[5]b = 0; //错:不能重新对b赋值
}
删除f()定义中的constexpr,main()函数中的各条语句是否正确?
constexpr int a = f(1);
与constexpr int b = a * 2 + 1;
会出错
将 constexpr int a = f(1) 改成 int a = 1 或 const int a = 1, main()中的各语句正确否?
改成 int a = 1:constexpr int b = a * 2 + 1;
和 int x2[b];
会出错。
改成const int a = 1是可以的。
#include <iostream>
struct A {int x;int y = 9;constexpr A(int a) : x(a) { } //对:所有成员变量要么有缺省值、//要么在初始化列表中constexpr A(int a, int b) : x(a) { y = b; x += y; } //对:函数体内是常量表达式constexpr A(int a) : x(a) { std::cout << x + y; } //错:函数体内调用了非constexpr函数constexpr A(int a) : y(a) { x = x + y; } //错:初始化列表中没有xA() : x(1), y(2) { std::cout << x + y; } //对:会生成普通的非constexpr对象
};
int main()
{int v = 10;constexpr A a(v); //错:非const变量的值不能在编译时确定(运行时才能确定), //因此a.x不能在编译时确定 constexpr A b; //错:构造函数A()不能产生constexpr对象constexpr A c(1); //对:生成constexpr对象cconstexpr A d(1, 2); //对:生成constexpr对象dA e(1), f; //对:e(1) = 去除constexpr属性, // f = 构造函数A()产生普通对象(非constexpr)A g(); //对:声明一个函数g(),其返回值是A的对象int z1[c.x], z2[d.x + d.y]; //对:c、d是constexpr的,c.x、d.x+d.y的值在编译时确定,等价 z1[1]、z2[5]int z3[e.x]; //错:e是普通对象(非constexpr的),e.x的值在编译时不能确定enum { X = c.x, Y = c.y }; //X = 1, Y = 9c.y = 10; //错:c是constexpr的,不能改变其成员e.y = 10; //对:e是普通对象f.y = 10; //错:对象 f 不存在???
}
作用域
- 程序可由若干代码文件(.cpp)构成,整个程序为全局作用域:全局变量和函数属于此作用域。
- 稍小的作用域是代码文件作用域:函数外的static变量和函数属此作用域。
- 更小的作用域是函数体:函数局部变量和函数参数属于此作用域。
- 在函数体内又有更小的复合语句块作用域。
- 最小的作用域是数值表达式:常量在此作用域。
- 常量对象的作用域是常量对象所在的指令行。
- 作用域越小、被访问的优先级越高。
- inline、static 修饰的变量和函数,作用域是模块文件内部,不同的模块可定义同名的 全局 或 inline、static 变量和函数
- 函数体内的{}构成块作用域:复合、switch、循环等语句。
- 同层块作用域可以定义同名变量,但他们是不同实体,值互相独立。
- 同层块作用域不能定义同名标号。故VS2019允许跨块转移,但转移位置必须在变量定义及初始化之前。
- VS2019允许向内层块转移,但转移位置必须在变量定义及初始化之前。
- 外层作用域的变量不要引用内层作用域的自动变量(包括函数参数),否则导致变量的值不确定。
- 全局变量和static变量永久存储在数据段,局部自动变量和函数参数存在于栈段,单值常量又称立即数理论上没分配内存。包含多个元素的常量(如对象、数组)实际上在数据段存储,但理论上认为没分配内存。
//假设1个项目(程序)由文件 A.CPP 和 B.CPP 组成
//A.CPP 的内容如下。
extern int y; //y是在B.CPP中定义的
int x = 1; //定义全局变量x,只能在A.cpp或B.cpp中共计定义一次
static int u = 2; //模块静态变量u,A.cpp或B.cpp均可定义各自的同名变量
static int v = 3;
static int g() { return x + y; } //A::x + B::y
inline int h() { return u; }
static int m() { return 1; }
int f() { //作用域范围越小,被访问的优先级越高int u = 4; //函数局部非静态变量:作用域为函数f内部static int v = 5; //函数局部静态变量:作用域为函数f内部return u + v + x + ::u; //f::u + f::v + A::x + A::u
}
//B.CPP 的内容如下。
extern int f(); //f() 是在A.cpp定义的
extern int x; //x是在A.cpp定义的
int y = 10; //y是全局变量
static int u = 20; //A.cpp和B.cpp都定义了u
static int g() { return x + y + u; } //A::x + B::y + B::u
inline int h() { return u; }
int main() {g(); //B::g( )h(); //B::h( )f(); //A::f( )m(); //errorreturn v; //error
}
生命期
- 作用域是变量等存在的空间,生命期是变量等存在的时间。
- 变量的生命期从其被运行到的位置开始,直到其生命结束(如被析构或函数返回等)为止。
- 常量的生命期即其所在表达式。
- 函数参数或自动变量的生命期当退出其作用域时结束。
- 静态变量的生命期从其被运行到的位置开始,直到整个程序结束。
- 全局变量的生命期从其初始化位置开始,直到整个程序结束。
- 通过new产生的对象如果不delete,则永远生存(内存泄漏)。
- 外层作用域变量不要引用内层作用域自动变量(包括函数参数),否则导致变量的值不确定:因为内存变量的生命已经结束(内存已做他用)。
试分析常量、变量和函数的生命期和作用域。代码文件:A.cpp,B.cpp
//A.cpp
int x = 2; //全局变量:生命期和作用域为整个程序
static int y = 3; //模块静态变量:生命期自第一次访问开始至整个程序结束
int f() //全局函数f():其作用域为整个程序,生命期从调用时开始
{int u = 4; //函数自动变量:生命期和作用域为当前函数static int v = 5; //函数静态变量:生命期自第一次调用开始至整个程序结束v++;return u + v + x + y; //f()的生命期在此结束
}
static int g() { return x; } //静态函数g():其作用域为A.cpp文件,生命期从调用时开始//B.cpp
extern int x;
static int y = 3; //模块静态变量:生命期自第一次访问开始至整个程序结束
extern int f();
static int g() //静态函数g():其作用域为B.cpp文件,生命期从调用时开始
{return x + y++; //x由A.cpp定义,y由B.cpp定义
}
void main() //全局函数main():其生命期和作用域为整个程序
{int a = f(); //函数自动变量a:生命期和作用域为当前函数const int&& b = 2; //传统右值无址引用变量b引用常量2:产生匿名变量存储2a = f(); //main()开始全局函数f()的生命期a = 3; //常量3的生命期和作用域为当前赋值表达式a = g(); //main()开始 B.cpp 的静态函数g()的生命期
} //为b产生的匿名变量的生命期在main()返回时结束