待解决
const_cast常量转换
constexpr函数
变量
声明与定义 extern
c++支持分离式编译,声明说明变量的类型和名字,而定义申请存储空间(或赋初始值)。
extern关键字在多个模块之间共享变量时很有用,在某个模块里定义,在另一个模块中用extern声明即可。
在file1.cpp
int global_int = 1;
在file2.cpp
extern int global_int;
//in some function
cout << "global_int = " << global_int;
注:如果变量是const常量,无论定义还是声明,都需要加extern。原因在于const默认作用域仅在文件内有效,需要扩大。
声明可以有多次,定义只能有一次
引用
对象是指占有一块内存区域,具有某种类型。引用不属于对象,只是变量的别名。一旦定义引用,无法将其绑定到其他对象,且引用定义必须初始化。
constexpr 变量定义常量
constexpr用于声明常量,用该关键字声明的变量一定是常量。
顶层const
指针本身是一个对象,它可以指向另一个对象。 名词顶层const表示指针本身是一个常量,底层const表示指针所指向的对象是一个常量。
const int *p;//p是顶层const,
int * const p;//p是底层const,不能给p赋值
decltype 自动类型推断
decltype作用是选择并返回操作数的数据类型。
模板函数中未知变量类型声明 关键字decltype
template <class T1, class T2>
void ft(T1 x, T2 y)
{
decltype(x+y) xpy = x + y;
}
int x;
decltype (x) y;//y类型与x相同
decltype 作用于函数时,返回函数类型而不是函数指针;
decltype作用于数组时,返回数组类型而不是数组首元素的指针;
字符 向量 数组
string
初始化
string s1;//默认空字符串
string s2(s1);
string s2 = s1;
string s3("value");
string s4 = "value";
string s5(n,'c');//s5为长度为n,每个元素都为c的字符串
string::size_type
string和其他标准库定义了几种配套的类型,类型size_type就是,其为size()函数返回的类型
auto len = line.size();//len 类型为string::size_type
此外,不要混用无符号类型和有符号类型
unsigned int int a = 1;
int b = -1;
cout << (a<b) <<endl;
//结果输出 1 ,因为负值会自动转化成一个较大的无符号值
string用于相加
两个string对象相加没问题,string与字面值相加,其中至少有一个为string对象,即加号两边至少有一个为string对象
string s1;
string s2 = s1 + "abc" + "def";//没问题
//等价于string temp = s1+ "abc"; string s2 = temp + "def";
string s3 = "abc" + "def" + s1;//error,加号两边至少有一个为string对象
string与for循环相结合
若要改变string里面的字符,需要用引用
string s("hello world");
for(auto &c :s)//引用
c = toupper(c);//变为大写
vector
{}初始化
默认情况下,()用于vector初始化,里面内容不是作为元素,通常带个数;{}里面内容通常作为元素进行初始化。如下:
vector<int > v1(10);//10个元素,每个都是0
vector<int> v2{10};//1个元素,值为10
vector<int > v3(10,1);//10个元素,每个都是1
vector<int> v2{10,1};//2个元素,值为10和1
当{}默认失败时,提供类似()的初始化功能,如:
vector<string>v7(10);//v7有10个默认初始化的元素
vector<string>v7{10};//v7有10个默认初始化的元素
for循环
for循环中,不应改变序列的大小。如vector用于for 循环,如果插入元素,原本存的end()迭代器会失效。
vector<int > v = {1,2,3,4,5,6};
for(auto &i:v)
i = i + 1;
不能以下标方式添加vetor元素。
数组
数组与auto decltype
auto a(数组名),a为指针; decltype b(数组名), b为数组
int ia[] = {1,2,3,4,5};
auto a(ia);
a = 42l//error a为指针
decltype(ia) b = {1,2,3,4,5};
b[3] = 4;
begin(数组名) end(数组名)
不同于标准库的 变量.begin(),变量.end(),数组的begin,end是数组作为参数
int ia[] = {1,2,3,4,5,6};
int *beg = begin(ia);
int *last = end(ia);//数组末元素的下一个元素
数组初始化vector对象
int arr[] = {1,2,3,4,5};
vector<int> ivec(begin(arr), end(arr));
vector<int> ivec(arr+1, arr+4);//ivec初始化为arr[1],arr[2],arr[3]
多维数组for循环
for语句处理多维数组时,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。这是为了避免数组被自动转化为指针。
int arr[3][4] = {1,2,3,4,5,6,7,8,9,10};
for(auto row:arr)//row被转化为指针,不能被col用for遍历,编译不通过
for(auto col:row)
cout << col;
//编译不通过
表达式
左值和右值
当一个对象被用作右值时,用的是对象的值(内容);当对象被用作左值时,用的是对象的身份(在内存的位置)。
使用关键字delctype时,如果表达式求值结果是左值,作用于该表达式得到一个引用类型;由于取地址运算符生成右值,所以delctype(&p)结果是int **, 是一个指向整数指针的指针。
算数运算符的运算对象和求值结果都是右值。
递增和递减
递增递减运算符有两种,前置:++i, 后置: i++ , 前置版本将对象本身作为左值返回,后置版本则将对象原始值的副本作为右值返回,一般不用后置版本。
*p++ , 常用的一直方式。
递增递减运算符会改变对象的值,所以在复合表达式中要注意。
whie(*p)
*p = toupper(*p++);//左右未知结果,因不知先递增还是先返回
c++异常处理
throw 表达式 引发异常/抛出异常
try 语句块 保护代码
catch{} 捕获异常,捕获与throw抛出相对应的异常, catch(…)可以捕获任何异常
示例:
try
{
int a,b;cin >>a >> b;
if(b == 0)
throw runtime_error("心态崩了");//抛出异常,初始化runtime_error异常类
cout << "a/b:" << a/b <<endl;
}
catch(runtime_error e1)//捕获异常,runtime_error为类
{
cout << e1.what() << endl;
}
异常类初始化:
exception,bad_alloc, bad_cast对象,不允许为这些对象提供初始值,只能默认初始化;其他类型不同,需要使用string对象或者C风格字符串初始化。
异常类成员函数:
异常类只有一个名为what的成员函数,返回const char* 类型。
异常处理流程:
异常抛出时,首先搜索抛出该异常函数,如果没找到匹配的catch语句,终止该函数,并在调用该函数的函数中继续查找,…, 如果最终未找到,则在标准库函数terminate中查找。
参考这个博客的异常再抛出
定义新的自己的异常
函数
函数声明
函数声明定义与变量类似,在头文件声明,在源文件定义。
参数传递
按值传递 指针形参 引用形参
按值传递,实参的拷贝传递给形参;指针形参虽然也是指针的拷贝值,但指针可以间接访问其所指对象,所以可以改变实参;引用是实参别名,可以改变实参。C++,建议使用引用类型的形参代替指针
引用还有一个好处是可以引用可以避免拷贝,拷贝大的类类型对象或者容器对象比较复杂,或者某些类型(包括IO类型在内)不能拷贝,只能通过引用形参。
main:处理命令行选项
main()函数可以传递参数,
int main(int argc, char *argv[]){...}
第一个形参agrc,表示数组中字符串的数量;第二个参数argv是一个数组,它是指向C风格字符串的指针,第一个元素指向程序的名字或空字符串,最后一个指针之后的元素值保证为0。 即argv实参,从agrv[1]开始.
initializer_list 形参
如果实参数量未知,但类型全部相同,可以使用该关键字,该关键字被定义在同名头文件里。
函数返回值
返回局部对象的引用是错误的,返回局部对象的指针也是错误的。
引用返回左值
调用一个返回引用的函数得到左值,其他返回类型得到右值。
char &get_val(string &str, int ix)//函数返回引用
{
return str[ix]
}
int main()
{
string s("abcdef");
get_val(s,2) = 'A';//将s[2]改为'A'
}
列表初始化返回值
函数可以返回花括号包围的值的列表。
使用尾置返回类型 (未完成)
auto func(int i) -> int(*)[10];//func返回的是一个指针,并且指向了10个整数的数组。
内联函数和constexpr函数
调用函数包含一系列操作,内联函数可避免不必要开销。内联函数使用inline声明
constexpr函数
constexpr size_t scale(size_t cnt){return new_sz() *cnt;}
int arr[scale(2)];//正确,scale(2)是常量表达式
int i = 2;
int a2[scale(i)];//错误
内联函数和constexpr都应该定义在头文件里
- 使用constexpr,要求返回类型和形参都是字面值类型。
- constexpr函数不一定返回常量表达式。
类
成员函数
定义在类内部的成员函数都是隐式inline函数
引入this
在成员函数nebula,可以直接使用调用该函数的对象的成员,这是通过隐式this实现的,即看似 直接使用item,实则是this->item。
this是一个常量指针,不允许改变this中保存的地址。
引入const成员函数
成员函数有时不需要改变对象本身,此时可以在函数后面加const,有利于提高函数灵活性。
class myClass
{
private:
void mytest() const// const 成员函数
{
...
}
}
const成员函数将this从 myClass *const 改为 const myClass * const, 这样有利于提高函数的灵活性。原因在于改之前,常量对象不能调用普通的成员函数
类编译顺序
首先编译函数成员,然后才编译成员函数,所以成员函数可以访问成员,而不论其声明顺序。
构造函数
- 构造函数不能声明为const
- 构造函数没有返回类型
- 只有类没有声明任何构造函数时,编辑器才会自动生成默认构造函数
构造函数初始值列表
class myClass
{
myClass(int a, string b) : height(a), name(b)
{
}
private:
int height;
string name;
}
有时候构造函数初始值列表可以通过在构造函数体内用赋值实现,以下情况只能用列表实现:
- 成员是const
- 成员是引用
- 成员属于某种未提供默认构造函数的类类型
委托构造函数
一个构造函数只完成部分成员初始化过程,需要通过其他构造函数来完成。
class myClass
{
myClass(int a, string b) : height(a), name(b)
{
}
myClass(int a): myClass(a,"test")//委托构造函数
{
}
private:
int height;
string name;
}
转换构造函数 隐式转换
转换构造函数,类似于默认初始化形式,对参数进行转化,比如:
item.combie(myclass a);//函数声明
string book;
item.combine(book);//转换,string实参创建了一个myclass临时变量,并将它赋给combine
只允许一步转换,比如item.combine(“9999”)是错误的,需要先转换为string再转换为myclass,
explicit抑制隐式转换
类内声明构造函数时用explicit,如果在外部定义,此时定义前面不需要加。
友元
友元作用:使得其他类或函数可以访问该类的成员。
友元分为声明和定义,
声明需要进行两次,一次出现在类的内部,作用是表明友元性质;一次是出现在类外面,使得类的用户可见,从而调用该友元。
class myClass
{
friend int add(myClass a);//一次声明
}
int add(my Class a);//二次声明
//未定义
通常,应将友元的声明和类本身放置在同一头文件中,即头文件既提供类内的友元声明,也提供类外的外部声明
类的声明
一个类的类型不能是他自己,只能是自身类型的指针或引用
class myClass
{
myClass *next;
myClass *prev;
}
名字查找和类的作用域(未完成)
一般流程:在名字所在块寻找其声明语句(名字之前);如果没找到,继续查找外层作用域,最终没找到,程序报错。
类不同于一般流程,类的编译过程:首先处理完类的全部声明才会处理成员函数的定义,因此成员函数可以用到类的所有定义的名字。
类成员声明的名字
typedef int test;
class myClass
{
test balace() {}
}
上面的test用于类成员声明,只在test balance之前查找,而不在后面查找;因为没有查找到,因此在外层继续查找。
类内不可重新定义外部的变量
一般来说,内层作用域可以重新定义外层作用域的名字,即时名字已经在内层使用过。但是类不允许。
typedef int test;
class myClass
{
test balace(){}
typedef int test;
test val;//error,即使与外部定义相同,也不允许
}
成员函数普通作用域名字查找
- 首先在成员函数内部,名字之前查找声明,如果没有
- 在类内继续查找,此时所有成员都可以考虑
- 类内也没查找到,在类外定义的作用域查找(之前)
类的静态成员
实现方式:在成员的声明之前加上关键字static。
- 类的静态成员存在于任何对象之外,对象不包含任何与静态数据成员有关的数据(但可以通过对象访问静态成员)
- 静态成员函数不与任何对象绑定在一起,不包含this指针
两种方式访问静态成员:
- 作用域运算符
class myClass
{
static void rate(){}
}
double r;
r = myClass::rate();
- 类的对象,引用或指针
myclass a1;
a1.rate();
myClass a2;
a2->rate();
- 类内其他成员访问静态对象不需要作用域运算符
myClass
{
void test()
{
rate();//不需要作用域运算符
}
static void rate();
}
- 定义静态成员
- 静态成员函数定义可以在类内或者类外,必须用static声明在类内,在类外定义时,不能再重复使用static;
- 静态数据成员不是由构造函数初始化的,必须在类的外部定义和初始化每个静态成员。