C++学习笔记1
1.变量声明和定义的关系
为了运行把程序拆分成多个逻辑部分来编写,c++语言支持分离式编译(separate compilation)机制,该机制允许将程序分割为多个文件,每个文件可被独立编译。为了支持这种分离式编译,c++支持将声明和定义区分开来。
声明:规定了变量的类型和名字,在这一点上定义与之相同。声明(declaration)使得名字为程序所知,一个文件如果想使用别处定义的名字必须包含对那个名字的声明。
定义:定义还申请了存储空间,也可能会为变量赋一个初始值。定义(definition)负责创建与名字关联的实体。
如果想声明一个变量而非定义它,就在变量名前添加关键字extern,而且不要显示地初始化变量:
extern int i; //声明i而非定义i
int j; //声明并定义j
任何包含了显示初始化的声明即为定义。当我们给由extern关键字标记的变量赋一个初始值时,其不再是声明,而是定义了。
extern double pi = 3.1416; //定义
在函数体内部,如果试图初始化一个由extern关键字标记的变量,将引发错误。变量能且只能被定义一次,但是可以被多次声明。
2.const 限定符
2.1 const含义
常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被修改的
2.2 const作用
- 定义常量
const int time = 2
常量定义时就必须初始化
- 类型检查
const定义的变量只有类型为整数或枚举,且以常量表达式初始化才能作为常量表达式。其他情况只是一个const限定变量,不要将其与常量混淆。
const常量与#define宏定义区别:const常量具有类型,编译器可以进行安全检查;#define宏定义没有数据类型,只是简单的字符串替换,不能进行变量类型的安全检查。
- 避免误修改
void fun(const int var){
var++; //error!
}
- 节省空间,避免不必要的内存开销
对于const定义的常量或者限定的变量,在程序中都分配了内存地址,所有使用该变量的地方都使用了相同的内存地址。而对于#define只是宏定义的一个数或表达式的替换,这样所有使用的宏的地方都协议拷贝,内存分配。因此造成了额外的内存开销。
2.3 const对象默认为文件局部变量
- 未被const修饰的变量在不同文件的访问
// file1.cpp
int ext
// file2.cpp
#include<iostream>
extern int ext;
int main(){
std::cout<<(ext+10)<<std::endl;
}
- const修饰的变量在不同文件的访问
//extern_file1.cpp
extern const int ext=12;
//extern_file2.cpp
#include<iostream>
extern const int ext;
int main(){
std::cout<<ext<<std::endl;
}
可以发现,非const变量默认为extern, const变量必须显示设定为extern.
2.4 指针与const
const char *a; //指向常量的指针
char const *a; //指向常量的指针
char* const a; //指向变量的const指针
const char* const a; //指向常量的常指针
总结: const离谁近就是在修饰谁。
- 指向常量的指针
const int *ptr;
*ptr = 10; //error
ptr是一个指向int类型const对象的指针。const修饰int,而不是ptr。因此ptr可以修改,但不能修改ptr所指向的对象的值。
- 不能使用void 指针保存const对象的地址,必须使用const void类型的指针保存const对象的地址。
const int p = 10;
const void * vp = &p;
void *vp = &p; //error
- 允许把非const对象的地址赋给指向const对象的指针:
const int *ptr;
int val = 3;
ptr = &val; //ok
2.5 顶层const & 底层const
指针本身是一个对象,它又可以指向另外一个对象。因此,指针本身是不是常量以及指针所指的是不是一个变量就是两个相互独立的问题。使用名词**顶层const(top-level const)表示指针本身是个常量,而用名词底层const(low-level const)**表示指针所指的对象是一个常量。更一般顶层const可以表示任意对象是常量,指针类型既可以是顶层const也可以是底层const。
int i = 0;
int *const p1 = &i; //不能改变p1的值, top-level const
const int ci = 42; //不能改变ci的值, top_level const
const int *p2 = &ci; //允许改变p2的值, low-level const
const int *const p3 = p2; //靠右的const是top-level const, 左是low-level const
const int &r = ci; //用于声明引用的const都是low-level const
3.常量表达式
常量表达式(const expression)是值不会改变并且在编译过程就能得到计算结果的表达式。字面值常量属于常量表达式, 用常量表达式初始化的const对象也是常量表达式。
const int max_files = 20; //max_files常量表达式
const int limit = max_file + 1; //limit常量表达式
const int sz = get_size(); //sz不是常量表达式
c++11新标准规定, 允许将变量声明为constexpr类型以便编译器来验证变量的值是否是一个常量表达式。
constexpr int mf = 20; //20常量表达式
constexpr int limit = mf + 1; //mf + 1是常量表达式
constexpr int sz = size(); //只有当size是一个constexpr函数时,才是一条正确的声明语句
新标准允许定义一种特殊的constexpr函数,这种函数应该足够简单使得编译时就能计算出结果,这样就能使用constexpr函数初始化constexpr变量。
在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关:
const int *p = nullptr; //p时一个指向整型常量的指针
constexpr int *q = nullptr; //q是一个指向整数的常量指针
constexpr int *np = nullptr; //np是一个指向整数的常量指针,其值为空
int j = 0;
constexpr int i = 42; //i的类型是整数常量
constexpr const int *p = &i; //p是常量指针, 指向整型常量i
constexpr int *pl = &j; //pl是常量指针,指向整数j
4.auto
c++11新标准引入了auto类型说明符,其使用编译器帮助我们推断类型。auto是通过赋予的值或者表达式来推断类型,因此使用auto声明变量,必须有初始值。
auto可以声明一行变量,但是所有的初始值类型必须相同
auto i = 0, *p = &i; //正确
auto sz = 0, pi = 3.14 //错误
auto一般会忽略顶层const
const int ci = i, &cr = ci;
auto b = ci; //b是一个整数, 忽略top-level const
auto c = cr; //c是一个整数, 忽略
auto d = &i; //d是一个整型指针(整数的地址就是指向整数的指针)
auto e = &ci; //e是一个指向整数常量的指针(对常量对象取地址是一种底层const, 即取地址得到的是指向常量的 //指针)
5.decltype类型指示符
c++11新标准引入了decltype类型说明符,帮助我们推断表达式的类型来定义变量a, 但是并不需要将该表达式赋值于a.
decltype(f()) a = x; //sum的类型就是函数f的返回类型
declype不会忽略顶层const
const int ci = 0, &cj = ci;
decltype(ci) x = 0; //x的类型是const int
decltype(cj) y = x; //y的类型是const int&
decltype(cj) z; //错误, z是引用,必须初始化
如果表达式或者变量可以作为左值使用,则decltype()返回引用类型。 变量加上一层或者多层括号,decltype()为引用类型。
int i = 42, *p = &i, &r = i;
decltype(r + 0) b; //正确, 加法的结果是int
decltype(*p) c; //错误: c是int&, 必须初始化
decltype((i)) d; //错误: d是int&类型,必须初始化
decltype(i) e; //正确: int类型
6.typedef
typedef char name, *pstring;
const name str; //const char
const pstring *ps; //ps是一个指针,指向char**的常量指针 != const char* *ps, 等价于 char ** const ps;
const pstring cstr = 0; //cstr是指向char的常量指针