变量和基本类型
引用与指针
- 引用一定要指向一个对象,且必须初始化,指针可以为空。
- 因为1,所以指针应该测试再使用,引用不需要测试。
- 引用指向的对象不能改变,永远是初始化时指向的对象的别名,指针可以反复改变。
什么时候应该使用指针,什么时候使用引用?
在以下情况下你应该使用指针,一是你考虑到存在不指向任何对象的可能(可设置指针为空),二是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。
如果总是指向一个对象并且一旦指向一个对象后就不会 改变指向,那么你应该使用引用。
重载操作符时应该使用引用。More Effective C++举例[]
*和&在不同时候有不同的意思
在声明语句中,和&用来表示复合类型,在表达式中则是运算符(&取地址符,解引用符)
void* 指针的特点
不能直接操作void*所指的对象
可以用于比较,可以用来作为函数输入输出或者赋值…….
其他:
不能把int赋给指针,是0也不行。
NULL、nullptr是cstdlib中定义的预处理变量,值为0,现在更推荐使用nullptr。
不允许指向引用的指针,指向指针的引用 例如int& 是可以的。
一条语句可以同时声明多个有关的变量,如 int a = 0, & b = a, c = &a;
int p[10] 表示一个包含10个整型指针的数组。
int (p)[10] 表示指向一个有10个int元素的数组的指针。
const限定符
const对象必须初始化。
const默认只在本文件内使用,在其他文件中需要extern修饰符。
底层const修饰的变量不能被修改,比如const指针既不能修改其指向,也不能修改其所指的地址内的内容,const引
用也一样。
对底层const的引用和指针也必须是底层const的。
顶层const表示指针本身是个常量。例如 int const p1 = &a; 不能将p1重新指向其他变量,const int p1可以。
可以同时实现顶层const+底层const:const int* const p3=p2; 右边const是顶层const,左边是底层const。但const int ci=42甚至const Node这表示的是顶层const,const int == int const。
constexpr 是顶层const,可以定义一些常量表达式。(编译阶段就能得到结果,值不会改变)。
常量指针:指针本身是常量
指向常量的指针:指向常量的指针。。。。
auto类型说明符
如果在一条语句内声明多个变量,所有变量的初始基本数据类型要一样。
auto a=1,b=1.2; 错
auto a=1,*p=&a; 对
auto一般会忽略顶层const,如果需要则明确指出。
如果auto不加引用,会直接生成一个新的对象/变量。auto &得到引用。
Node n;
const Node& fn = n;
auto w = fn; //w是Node型
w.a = 1; //可执行
Node n;
const Node& fn = n;
auto& w = fn; //w是const Node&型
w.a = 1; //不可执行
Node n;
const Node* fn = n;
auto w = fn; //w是const Node*型
w->a = 1; //不可执行
decltype类型指示符
decltype(f()) sum = x;
声明变量x类型为f()的返回值类型
表达式
cast转换
static_cast,只要不包含底层const都可以用
const_cast,只能用于改变const,不可改变类型
reinterpret_cast 强制转换,直接重新解释
dynamic_cast 用于隐式转换,比如非const变const,多态中的向上向下(含有虚函数的类)。
string m=static_cast
旧式转换type(expr)和(type)expr是先尝试const_cast和static_cast看是否合法,都不能则reinterpret_cast
函数
static 可定义局部静态对象。
指针形参也是拷贝传参,把指针的值拷贝到了形参上,就可以访问指针所指的内存。
省略符形参对应的实参无需类型检查。
不要返回局部对象的引用或指针。
参数使用initializer_list,可以用可变形参。与vector相似,但对象中的元素是常量值。
main函数不能调用自己,不能重载。
可以多次声明同一个函数,但在同一个作用域内一个形参只能被赋予一次默认实参。
void func(int a, int b, int c = 36);
void func(int a, int b = 5, int c);
int main()
{
func(99);
return 0;
}
这段代码在vs上过不了,但C++ primer觉得ok
局部变量不能做默认实参,其他的只要能转换就能做默认实参。但是改变变量可以改变该默认实参。p213
如何声明含有一个数组指针的函数
直接声明:
int (func(int i))[10];
别名:
typedef in arrT[10]
或者 using arrT = int[10]
arrT func(int i)
使用尾置返回类型:
auto func(int i) -> int (*) [10]
使用decltype
int odd[]={1,3,5,7,9};
int even[] ={0,2,4,6,8};
decltype(odd) *func(int i){
return ...;
}
const在重载时的不同表现
Record lookup(Phone)
Record lookup(const Phone)重复声明
Record lookup(Phone*)
Record lookup(Phone* const)重复声明
Record lookup(const Phone*) 新函数
Record lookup(Phone&)
Record lookup(const Phone&) 新函数
constexpr可以定义能用于常量表达式的函数。
inline定义直接展开的内联函数
assert在未定义NDEBUG时有效。可以使用NDEBUG定义自己的条件调试代码。
func获取当前函数名字
FILE文件名(字符串)
LINE 当前行号(整型)
TIME文件编译时间(字符串)
DATE文件编译日期
函数匹配
有重载时如何进行函数匹配?
- 根据函数名及可见性找出候选函数
- 选出能被调用的可行函数
- 寻找最佳匹配(如果存在)
- 如果含有多个形参,如果有且只有一个函数满足”每个实参的匹配度都不劣于其他可行函数需要的匹配,至少有一个实参匹配优于其他可行函数”则匹配成功。
实参到形参类型转换分为几个等级
- 精确匹配:类型相同;从数组类型或函数类型转换成对应的指针类型;删除或增加顶层const
- 通过const转换实现的匹配
- 通过类型提升实现的匹配
- 通过算术类型转换或指针转换实现的匹配
- 通过类类型转换实现的匹配
函数指针
函数指针和返回指针的函数
bool pf(const string& , const string&) 返回指针的函数
bool (pf) (const string& , const string&) 函数指针
pf=&lengthCompare
把函数名作为一个值时,自动转换成指针。
有重载时,函数指针必须精确匹配其中一个。
函数指针可以作为形参
void useBigger(const String &s1, const String& s2, bool pf(const String &s1, const String& s2))
等价于
void useBigger(const String &s1, const String& s2, bool (*pf)(const String &s1, const String& s2))
也可以使用typedef或者decltype得到函数指针类型,直接放在函数声明中。
typedef bool Func(const String&, const String&);
typedef decltype(compareString) Func2;
//以上两个类型等价都是函数类型
typedef bool (*FuncP)(const String&, const String&);
typedef decltype(compareString) *FuncP2;
//以上两个类型等价都是函数指针类型
void useBigger(const String &s1, const String& s2, Func);
void useBigger(const String &s1, const String& s2, FuncP);
//以上等价,因为函数名作为形参时会自动转换为函数指针
当decltype作用于函数时返回的是函数类型,需要指针则需要加上*
返回指向函数的指针
直接声明
int (f1(int))(int,int)
类型别名
using F=int(int,int);
using PF=int()(int,int);
PF f1(int);//正确
F f1(int);//错误
F f1(int);//正确
尾置指针
auto f1(int) -> int()(int, int)
decltype 略,记得加*
类与对象
访问控制和封装
class和struct唯一区别就是默认的访问权限不同。class默认成员private,struct默认publiic。
封装能够隐藏实现细节,实现了类的接口和实现的分离。
C++这里接口就是说可以被访问的函数,java中的接口interface更相近于C++中的抽象类。
封装的两个优点:
1.保护,限制用户代码对private成员的修改和访问。
2.方便修改代码,可以自由修改private成员,不用修改用户代码。
类成员及成员函数
可以定义类型成员,如using std::string::size_type pos; 或者typedef 类型成员一样存在访问限制。
类成员函数
定义在类内部的成员函数自动inline
也可以显示inline定义在类外部的成员函数(即使在类内部声明的时候没有写inline)
可变数据成员
加上mutable修饰的成员,可以被const成员函数修改。
返回*this的成员函数
如果从const返回,返回的也是一个常量
如果从引用返回,返回的就是对象本身
如果不引用,返回的是一个副本
可以根据是否const函数重载。
友元
将其他类或者函数声明为类的友元,则也可以访问private成员。声明方法:friend+常规声明,只能出现在类定义的内部,位置不限。
为了使类内的函数能够访问友元函数,友元函数本身需要在类外进行声明。
A类中声明B类为友元,则B类可以访问A的private。
友元不具有传递性。如果A声明B是友元,B声明C是友元,C不是A的友元。
可以将其他类的函数声明为友元,需要显示写出类名::函数。
这种情况下顺序:
1.定义A类,其中包含aaa()函数但不能定义。
2.定义B类且包含对A::aaa()的友元声明。
3.定义A::aaa(),这时候才能使用B的成员。
如果想要把一组重载函数声明为友元,需要逐个声明。
即使友元函数定义在类的内部,也必须重新声明该函数,否则不可以访问。(并不是每个编译器都这样)
构造函数
可以通过一个实参调用的构造函数定义一条从构造函数的参数类型像类类型的隐式转换的规则,但只允许一步转换(不能经过一个中间类型)。将构造函数声明为explicit可以抑制这种隐式转换。这种隐式转换只能用于直接初始化(即调用函数)而不能用等于号。
聚合类
只有public成员,没有定义任何构造函数,没有类内初始值,没有基类,没有virtual函数。
可以用花括号列表初始化,顺序必须与声明顺序一致。
Data val = {0,”anna”};
如果个数少于类成员数,默认初始化靠后的成员。
字面值常量类
个人理解这个字面值常量类就是编译的时候能确定内容的类?
数据成员都是字面值类型的聚合类是字面值常量类。
如果类符合以下条件也是字面值常量类:
- 数据成员都是字面值类型
- 类必须至少含有一个constexpr构造函数。(也就是一次性初始化所有类成员且不包含语句,用constexpr修饰)
- 必须使用析构函数的默认定义
- 如果数据成员含有类内初始值,该值必须是一条常量表达式(const int…之类的)。
类的静态成员
static修饰,不能用this也不能声明成const的。
可以使用静态成员作为类内函数的默认实参。
静态成员和指针一样可以是不完全类型。
其他常见问题
C++中指针和引用的区别
- 在概念上认为指针有空间而引用没有,虽然汇编代码显示引用也是和指针一样实现的。
- sizeof指针得到的是指针大小,sizeof引用得到的是引用对象的大小
- 指针可以不初始化或者初始化为NULL,引用必须初始化为某个具体对象的引用
- 指针可变,引用不可变
- 指针可以多级,引用只能一级
四种cast转换
见cast转换
static的作用
- 全局静态变量
- 局部静态变量
- 静态函数
- 类的静态变量
- 类的静态函数
c++中的智能指针
shared_ptr 获取引用计数use_count unique make_shared<>()
unique_ptr
weak_ptr 不能直接使用,不增加引用计数,
为什么析构函数必须是虚函数?为什么默认的析构函数不是虚函数
什么是右值引用
C++从文本到可执行文件经历的过程
预处理(文件包含、宏替换)->编译(转换成汇编代码)->汇编(转换成机器码)->链接(链接目标文件和库成可执行文件)
虚函数表和虚函数指针的关系,是在哪个阶段实现的
编译和底层 一窍不通
c++11的新特性
增加shared_ptr,weak_ptr等智能指针
auto关键字
nullptr关键字
可用初始化列表对类进行初始化
右值引用
atomic原子操作用于多线程资源互斥操作
新增array和tuple