一:
g++c1.cpp -o c1.exe -std=c++11
c++11,加入了int a = {10},的初始化形式,如果有精度损失,那么会有警告。
二:
对:const int引用= int //const 只是针对自己来说的
错:int 引用 = constint //不符合逻辑与语法,引用不是常量,说明可以改,但引用的却是常量,矛盾了。
三:
引用的初始化(绑定)必须是类型一致:除了
1:常量引用可以是,可转换成引用的类型的。
因为是常量引用,既然不能改,编译器的实现方式是借助一个临时常变量。
double dval = 3.14;
const int &ri = dval;
实际却是:
const int temp = dval;
const int &ri = temp;
其实是绑定到了一个临时量。既然不能改,也合理。(指针没这么做),这方面指针是错误,引用是警告。
2:继承关系 &父 = 子。
四:
int i =-1;
constint ic = 10;
constint *p1 =&i;
int*const p2 =&i;
constint *const p3 = ⁣
//注释的都是我预估错了的
i= ic;//y,普通变量可以,对于引用和指针不行
p1= p3;//y
p1 = ⁣//y
//p3 = ⁣//no p3的指向不能变了
//p2 = p1;//no
//ic = *p3;//no ic的值不能变了
五:
顶层const,对变量本身而言
底层const,与指针,引用等复合类型的基本类型部分有关。用于声明引用的const,都是底层const
别名声明: using SI = Sales_item; 相当于 typedefSales_item SI;
typedef char *pstring;
const pstring cstr = 0; 相当于 char * const cstr =0;
注意,pstring 基本数据类型是char , 而如果是 const char * cstr =0;基本数据类型就成了 const char。
六:处理类型
(1)auto
1:对于普通的类型就是值赋值
2:对于复合数据类型,因为赋值之后,还要有关系,所以需要注意
3:(书上说对顶层const忽略,底层const保留,想起来比较复杂,暂时忽略)
例如:
int i = 0;
const int ci = i,&cr = ci;
auto b=ci;//b是一个整数
auto c = cr;//c是一个整数(引用只是别名,赋完值就没什么关系了)
auto d = &i;//d是一个int 指针
auto e = &ci;//e是一个指向整数常量的指针(赋完值还有关系推断)
(2)decltype
返回实际的类型,例如 const int i = 1;decltype(i) x= 0;//x是const int
1:解引用操作返回的是引用类型。
int i = 20,*p=&i;
decltype(*p) c = &i;//c是int&类型,必须初始化
2:如果给变量(表达式)加括号,会得到引用类型
decltype((i)) d = &i;//d是int&类型,必须初始化
七:初始化
c++11 类内成员可以提供一个初始值。
1:拷贝初始化(=)只能提供一个值
2:类内初始值,只能使用 = 或者{ },不能用( ),防止用类型初始化当成函数。有歧义。
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2628.html#konaissue
3:初始元素值的列表,只能用{ }花括号。(多个)如vector(string不是列表,所以可以)
八:
string str = “Hello”;
str[n] //返回str中第n个元素的引用。
拷贝初始化 =
(值/默认)直接初始化 () 或{},例:只提供数量 如 vector<int> vec1(10);//10是数量默认初始化。
列表初始化 { }
如果循环内部有增删vector的元素,那么不能使用“范围for”,因为对应的迭代器会有失效问题。
vector对象类型总是包含着元素的类型。
vector<int>::size_type //正确 (无符号类型)
vector::size_type //错误
两个迭代器的距离类型:difference_type (带符号整型)
两个指针相减的类型 ptrdiff_t (带符号类型)
下标类型size_t (无符号)但内置的下标类型可以取负数。(这个没那么严格,取得不对就是未定义的错误)
constexpr //顶层const
如:constexpr int *p = &i; 等于 int * const p = &i;
定义数组时必须指定类型,不允许使用auto根据列表判断类型。
数组名在auto推断时,只是一个普通的指针,并不是const指针
intia[] = {0,1,2};
autoia2(ia); //类似于autoia2(&ia[0]);
intit = 10;
ia2= ⁢
cout<<*ia2<<endl;
decltype返回的类型最精确:
intia[] = {0,1,2};
decltype(ia)ib = {5,6,8};
for(autoc:ib)
cout<<c<<endl;
九:
无法保证c_str()返回的数组一直有效。
用数组初始化vector,只需指明要拷贝区域的首尾地址即可。
int arr[] = {0,1,2};
vector<int>ivec(begin(arr),end(arr));
十:
所有的范围for都可以改写成普通的for(i,j)。
但是引用却不一定都可以改写成指针。毕竟不是同一个东西,各有各的用途。就好象数组名在作为参数传递时会蜕变成指针,但是并不是说任何地方都可以替换成指针,比如sizeof就没法互相替换。
防止数组被自动转换成指针,可以通过引用实现。
(使用范围for处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型)
for(auto &row:ia)// 不然会自动转换,导致编译不过
for(autocol:row)
数组其实有类型,只是我们只在定义的时候会用到,其他情况(强转成数组类型不行)。
#include<iostream>
using namespace std;
int main()
{
intia[2][3] = {
{0,1,2},
{3,4,5}
};
//using int_array = int[3];//类型别名
//typedefint int_array[3];
//for(int_array*p=ia;p!=ia+2;++p)
// for(autop=begin(ia);p!=end(ia);++p)
for(autop=ia;p!=ia+2;++p)//auto p可以替换成int(*p)[3]
{
//for(autoq=begin(*p);q!=end(*p);++q)
for(autoq=*p;q!=*p+3;++q)//auto q可以替换成int *q
{
cout<<*q<<‘‘;
}
cout<<endl;
}
return0;
}
//int(*p)[3]这就是数组类型,指针是替换不了的。
//using int_array = int[3];//类型别名
并不是所有的数组都可以用指针替代
并不是所有的引用都可以用指针替代
#include<iostream>
using namespace std;
int main()
{
int ia[2][3] = {
{0,1,2},
{3,4,5}
};
//范围for处理多维,必须是引用
//auto &row:ia
for(int (&row)[3]:ia)//int (*row)[3]:ia)
{
for(int col:row)
{
cout<<col<<‘ ‘;
}
cout<<endl;
}
return 0;
}
引用的一大好处就是,避免元素的拷贝,节省空间和效率。
比如对于vector<string>vec的范围for遍历
for(auto &c:vec)//避免拷贝。这里不能改成指针,因为vector存的不是地址
十二:
除法运算,商的符号(两个运算对象符号相同,则为正,相反,则为负)
模除,符号与被除数的符号一样。因为要符合m = (m/n)*n+m%n 的原则。
类型转换的趋势:向无符号,向大空间 转化
decltype
&取地址
sizeof
typeid
数组名到指针的自动转化不会发生。
强制类型转换:
(1)static_cast
(2)const_cast只能改变运算对象的底层const
如:constchar *pc; char *p = const_cast<char*>(pc);可以通过p写了
(3)reinterpret_cast(为运算对象的位模式提供较低层次上的重新解释。
如:int*ip; char *pc=reinterpret_cast<char*>(ip);
十三:
initializer_list 与vector不一样的是前者永远是“常量值”
范围for的条件是对象要有bengin和end函数,数组和string及initializer_list都有。
varargs:
void CLogFile::vWriteLogEX (constchar *format,...)
{
char szMsg[MAX_LOG_LENGTH + 1];
memset(szMsg,0,sizeof(szMsg));
/* 内容 */
va_list argp;
va_start(argp,format);
//还是调用库函数对不定参数解析,而不是自己解析 _vsnprintf_s(&szMsg[strlen(szMsg)],MAX_LOG_LENGTH,_TRUNCATE,format, argp);
va_end(argp);
m_queueQueryMsg.push(szMsg);
}
递归函数:N的阶乘。
平时总是这么写:
int fun(int n)
{
if(n==1) return 1;
else return fun(n-1)*n;
}
先处理终结条件。没有任何对异常输入的判断(负数等)
另一种写法:
int fun(int n)
{
if(n > 1) return fun(n-1)*n;
return 1;
}
十四:
返回数组指针:
#include<iostream>
#include<string>
#include<vector>
#include<stdexcept>
usingnamespace std;
int odd[]= { 1, 3, 5 };
inteven[] = { 0, 2, 4 };
int(*fun(inti))[3]
{
returni % 2 ? &odd : &even;
}
//尾置返回类型
auto fun2(inti) ->int(*)[3]
{
returni % 2 ? &odd : &even;
}
decltype(odd) *fun3(inti)
{
returni % 2 ? &odd : &even;
}
usingint3Arr =int(*)[3];
int3Arr fun4(inti)
{
returni % 2 ? &odd : &even;
}
int main(intargc,char
**argv)
{
int (*ret)[3] = fun3(2);
for (int i = 0; i < 3;++i)
{
cout << (*ret)[i]<<‘ ‘;
}
return 0;
}
顶层const(不影响传入函数的对象)不能实现重载,底层const可以实现重载(指针const int * p ,和引用)
编译器确定重载函数的过程:
1:候选函数(可见作用域内,相同的函数名字)
2:可行函数(参数数量,和类型找最优)
内联机制用于优化“规模较小,流程直接,频繁调用”的函数。
常量表达式:由类型和初始值确定。
constexpr函数:能用于常量表达式的函数。要求:
(1)函数的返回类型及形参的类型都得是字面值类型。(算术,引用,指针)(string,io,自定义类型都不行)
(2)函数体有且只有一条return 语句。(也可以有运行时不执行任何操作的空语句,类型别名和using声明)
constexpr函数不一定返回常量表达式。constexprint fun(int i){return i*2};认为这不是常量表达式。
constexpr函数被隐式的指定为内联函数(为在编译过程中随时展开)
函数指针:
usingFun =void(long);
usingPFun =void(*)(long);
函数可以返回PFun类型,不能返回Fun类型。但是作为函数的参数,Fun会自动转成函数指针。(也就是说参数是Fun,PFun的函数是同一个)
typedef int(*Fun)(int);
auto fun(int) ->int(*)(int) //尾置返回类型。
decltype(fun)
十五:类
抽象:数据成员,成员函数
封装:访问属性(private,protect,public)
this 是常量指针,但不是指向常量的指针。根据初始化规则,我们不能把this绑定到一个常量对象上。
不能在常量对象上调用普通成员函数。(this的类型不同),常量对象(引用和指针只能访问常量成员函数)
常量成员函数的const的作用就是把this声明成指向常量的指针。所以编译器会对const成员函数做限制,对象的属性都是const的,不能访问普通成员函数。
编译器分两步处理类:1:处理所有声明2 :再处理函数体(函数体在整个类可见后才会被处理)
IO类属于不能被拷贝的类型,并且读取和写入会改变流的内容,所以
1:只能用引用传参
2:不能是const引用
对于一个普通的类必须定义自己的构造函数:
1:如果我们自己定义了构造函数,编译器就不会再定义默认构造函数。(依据:如果一个类在某种情况需要控制对象初始化,那么可能在所有情况都需要控制);
2:内置类型或复合类型(数组,指针),默认的构造函数中值是未定义的。
3:包含其他类类型的成员,而那个类没有默认构造函数。
如果我们需要默认的行为,就在参数列表后面加上 = default;这样就会有默认构造函数了。
拷贝,赋值,销毁 ;编译器会替我们合成,但是有时候合成的版本不能正在工作,特别是需要分配类对象之外的资源时(如管理动态内存的类),vector和string可以。
友元不受访问控制级别的限制。因为他不是类的成员。
友元仅仅指定了访问权限,并不是声明。所以必须在友元声明之外再单独声明。
友元没有传递性。
类和非成员函数的声明不是必须在他们的友元声明之前,编译器会隐式假定友元的声明名字在作用域内是可见的。即使在类的内部定义该函数,也必须在类外提供相应的声明,使函数可见。
友元类的“成员函数”(对象不可以)可以访问此类的非公有成员,所以,可以认为友元类的作用是把自己的所有的成员函数声明称另一个类的友元。
mutable可变数据成员,即使const成员函数也可以改变他的值。(而volatile由程序直接控制之外的过程控制,如系统时钟更新的变量,mutable是在程序控制之中的改变。)
对于常量函数的const可以重载(底层const可以重载)
类没定义,只是声明 :不完全类型。
不完全类型:(1)可以定义指针和引用。(2)可以声明(但不能定义)以他为参数或返回类型的函数。
函数的作用域运算符只限定参数列表和函数体,如果返回类型也是作用域中才有的,那么返回类型也要另外加上作用域运算符。
类内不能重新定义已经被类内使用过的类外定义的类型。所以,类内对于类型的定义,为避免出错,总是放在类的最开始位置。
如果成员是const,引用,或者某种为提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初始值。
初始化顺序与他们在类中定义的顺序一致,与在列表中的位置无关。
如果有默认的实参,那么也可能作为默认构造函数。
委托构造函数:把自己的一些职责委托给其他构造函数完成。
如CA():CA(“”,0,0){ }
转换构造函数:只有一个实参的构造函数,定义了从参数类型向类类型隐式转换的规则。
编译器只会自动的执行一步类型转换。(如string),但是内置类型的转换除外(可以多步)
对于有一个实参的构造函数,要抑制隐式类型转换,需加上explicit
聚合类:没有类内初始值,没有构造函数,没有基类和虚函数。都是public
更像最开始的结构体类型。
构造函数不能被声明成const的,直到构造函数完成初始化过程,才真正完成“常量”属性。
字面值常量类:虽然构造函数不能是const的,但是字面值常量类必须有个constexpr构造函数;
基于构造函数不能有返回值,而constexpr函数必须有且只有一条返回值。
那么一般我们用(必须初始化所有数据成员,构造函数列表初始化)
1:=default 使用默认的
2:=delete 只声明不能使用
3:函数体定义成空。
静态成员属于类,不属于对象。初始化必须在外部(内部现在也可以),私有静态函数初始化也没事。
如:
classCA
{
public:
staticstring name;
private:
staticstring setName()
{
return"wanggang";
}
};
stringCA::name= setName();
因为CA::之后就指明后面的都是CA作用域内的。
静态成员函数不包含this指针,而常成员函数的const是用来修饰this的,所以静态成员函数不能声明成const。
静态成员函数不能访问普通对象和函数,编译不过:“非静态成员引用必须与特定对象相对”;
1:静态成员的类型可以是他所属的类类型(不完全类型),而非静态成员只能是引用或指针。
2:静态数据成员可以作为默认实参。
class CA
{
public:
void fun(int a = sia);
static int sia;
}
常成员函数const声明和定义都要有。
=default在内部就是内联的,在外部就不是内联的。(只能声明,不能定义),不能内外都有(提示已有主体)
外部定义静态成员时,不能重复static关键字。(此处不能制定存储类)
const static 成员可以在类内初始化,类内和类外,只能初始化一个。(否则就是多次初始化)
inline内外都可以有。
virtual只能在类内的声明处。类外定义处不能有。
noexcept声明和定义,要么都有,要么都没有。需要在const/引用限定符之后,final,override和虚函数=0之前。