C++学习笔记01
1.main的返回值被用来指示状态,返回值0表示成功;
2.endl:换行+清空缓冲区,将缓冲区内容刷到io设备;
3.“>>“”是输入运算符,左边必须是istream对象,cin>>x>>y>>endl的运算是先从外设输入到变量,返回cin,然后再输入y(>>是重载过的);
4.char8位,short16<=int16<=long32<=long long64;
float 6位有效,double10位有效;
5.比特序列存储,可寻址的最小内存块为字节,内存的基本单元称为字
字节都是8比特,字可能是4字节或8字节;
-128~127;
-1的比特序列1111 1111(signed char255);
unsigned char的256是无定义的;
6.有符号和无符号运算:都转无符号,有符号的先写成二进制码再补码再换成十进制运算;
7.double Id = 3.14,然后int a{Id},b={Id}都会报错,而int c(Id),d=Id正常执行且数据丢失;
8.extern int i;//声明i而非定义i,在别的文件定义了;
9.名字的作用域:以花括号为间隔,就近(往下直到花括号),显式访问是变量前加:: ;
10.引用:int i = 1024; int &rei = i;(就是&是引用int类型,理解为绑定/起名字,且引用必须初始化,不能直接用非常量引用,会出现风险);
而在使用的时候,rei = 2;(相当于i = 2)int b = rei;(相当与b=i)[引用都是指向同一个对象,不是开辟新的,只是多个名字];
11.指针:(对地址的封装,本身是一个对象p),不能定义指向引用的指针。取地址符&获取指针封装的地址
int i = 10;
int (*p) = &i;//(注意double *p = &i;是错的,类型不匹配);
//解引用符/取对象符*利用指针获取对象
std::cout << *p;
12.void *指针(纯粹的地址,与指向类型无关)
//13.指向指针的指针:
int *p = &i;
int **pi = &p;
//指向指针的引用:
int i = 1024;
int *p;
int *&r = p//(相当于int * (&r) = p,r是p的别名,即r = &i);
14.const限定符:const int i = 42;(必须初始化)
跨文件要加extern
//const的引用
const int ci = 1024;const int &ri = ci;//(对)
const int ci = 1024;int &r2 = ci;//(错,有通过改r2来改ci的风险);
15.不能用变量的引用指向常量
//指向常量的指针:
const int i = 3;int *ptr = &i;//(错,存在通过ptr修改i风险)
const int *cptr = &i;int j = 5;cptr = &j;//(对,不存在通过cptr修改j);
//const指针:
int *const cur = &p;//(不变的是地址,内容可变,除非int前面有const,不然就是个变量);
找const的最右边,如果是*p(int *p = &a),则表示a的值不变,如果是p(*const p),则表示a的地址不变;
constexpr int *p = 0;(和int *const p = 0;表示是一样的,表示地址不变)
16.
常量指针:const int *p,不能通过指针修改指针指向的变量的值,但指针可以指向别的变量,即指针所存的地址可变(p = &b;是可以的)】
指针常量:int *const p,不能指向别的变量,但是可以通过指针修改它所指向的变量的值(*p = 20;是可以的)】
顶层const:表示变量本身是常量【int *const p = &i;(指针情况),const int p = 42;(变量情况),都是最右边是p】
底层const:表示指针所指向的对象是一个const(const int *p2 = &ci;)
17.
错误都是将变量绑定到常量:int *p = p3;(注:p3是const)是错的,const int &r2 = i;(注:i是变量,没const)是对的,i没有风险;
常量表达式:在编译的时候就要得出结果,且开头一定是先用const固定了变量:const int p = 20;
constexpr变量(IO库,string,自定义类型不能用):constexpr int p = 20; (表示意义和上面一样);
constexpr int *p = 0;(表示常量指针,表示的意义和int *const p一样);
typedef char *pstring;const pstring cstr = 0;(注:这里不是简单的替换成const char *cstr,这里的const是直接修饰cstr的,即是地址能换,内容不能换的常量指针);
18.auto:让编译器推断类型
(auto e= &c;这句语句理解是首先是给了c的地址给e,则e是个指针,再看c是不是有const限定,如果有则理解为内容不能变)
(const int c = i;auto b = c;如果不是指针,const是不用管的,会被忽略,auto会自动变成int,要加const的话要自己加,如:const auto b =c;)
(const int c = i;auto &g = c;这里的引用是没有忽略const的,即const int &g = c;)
(auto &h = 43;是错的,存在h的风险,const &h = 43;是对的,可以用常量引用绑定字面值)
(int c = 0;const int d = c;auto &m = c,p = &d;这三句的理解是先看&m = c,c是int型,auto变成int,然后用int看p = &d,即int *p = &d;这里就出问题了,因为d是const的,但是前面的是变量指针,存在风险修改d)
19.
decltype类型说明符(返回类型):const int a = 0;decltype(a) x = 0;(//相当于const int x = 0);const int &c = a;decltype© y = x;(相当于是const int &y = x,注意别漏了&);
注:如果decltype()里面是解引用操作(*xx)或者是decltype((v))【双括号】,则得到的是引用类型,即后面的内容必须要进行初始化;
20.
预处理器(头文件保护,确保头文件只编译一次)
#ifndef XXXX
#define XXXX
#endif
命名空间的using声明:[using namespace::name]例如using namespace std(头文件里面不要用这个)
标准库类型string:长度可变的字符序列
a>>b:从a中读取字符串赋给b,字符串以空格分隔,返回a
getline(a,b):从a中读取一行赋给b,返回a 例如:getline(cin , b)是从输入流中读取到b
a.size():返回a的字符个数
a[n]:返回a的第n个字符的引用
string s1 = “hello”;string s2 = s1 + “world”;(正确) string s2 = “a” + “b”;(错) string s2 = “a” + “b” +s1;(错) string s2 = “a” + (“b” + s1);(对)
cctype头文件
string b = “abcdefg”;
for(auto a:b)//遍历取出b的每一个字符
for(auto& a:b)//用引用的方式可通过c改b
21.
标准库类型vector:对象的集合【和string用法类似】
vector是模板,由vector生成的模板必须包含vector中元素的类型,如vector(可以理解为装int型的容器,是一个新的类型)【注:引用不是对象】
初始化vector对象的方法:
vector v1;//v1是空vector,它潜在的是T类型
vector v2(v1);//也可以是vector v2 = v1;
vector v3(n,val);//v3中包含n个重复元素,每个都是val
vector v4(n);//v4包含了初始化的n个对象
vector v5{a,b,c,d…};//也是vector v5 = {a,b,c,d,…} v5包含的赋予初始值的多个元素
【注:需要用for语句时尽量用范围for,避免溢出,如vector v{1,2,3}; for(auto &i:v),还有一个是如果要修改本来的值,就要用引用&】
v.push_back(a);//将a转入容器v
迭代器iterator(类似指针):有迭代器的类型都拥有begin和end成员;
空的:v.begin() == v.end();
begin:返回指向第一个字符的迭代器v.begin() ,end:尾后迭代器,v.end()即尾元素的下一个位置(一个本不存在的元素,地址是存在的);
标准容器迭代器的运算符:
*iter:返回迭代器item所指元素的引用
iter->mem:表示与先解引用,再调用成员mem
++iter/–iter:表示item指向下/上一个元素
iter1 == iter2;两个迭代器指示的内容相同
迭代器类型iterator和const_iterator(和常量指针差不多,不能修改对象内容)
用法:类型::iterator it;【如vector::iterator it1;//it1能读写vector元素】;
如果对象v是常量(const vertor),则a = v.begin()和a = v.end()中的a类型是vertor::const_iterator,变量则是vertoe::iterator
注:新版C++中变量要拿到常量迭代器,则用v.cbegin()和v.end()
22.
(*tmp).empty()//(可写成tmp->empty();)
任何试图改变vector对象容量的操作都会使迭代器失效
迭代器运算:iter+n;//迭代器下移n
23.
字符数组的最后面必须有一个空字符,数组间不能直接拷贝,赋值
int (*ptr)[10] = &arr;//首先小括号的优先级最高,ptr是一个指针,其次去掉()看,ptr指向一个含有10个整数的数组
int (&ptr)[10] = arr;//首先小括号里是一个引用,ptr是一个引用,其次是引用的是10个整数的数组
int *(&ptr)[10] = arr;//首先小括号里是一个引用,去掉小括号后是int *[10],表示引用的是一个含有10个int型指针的指针数组
24.指针和数组(数组在编译时会变成指针):
string *p = &num[0]//(等价于string *p = num)
auto p2(num);//num返回的是第一个元素的地址,将地址当作值赋给变量p2时,变量p2是一个指针,指向num的第一个元素,等价与p2(&num[0])
int arr[5] = {};int *p1 = arr +5;//(正确,指针指向尾后);int *p2 = arr + 10;(错误)
25.C风格字符串:长度:strlen§ 比较:strcmp(p1,p2)。p1>p2输出正数,等2小负 拼接:strcat(p1,p2) 复制:strcpy(p1,p2)p2给p1
注:C语言的字符串string没默认成数组?string s(“option”);char *p = s;(后面会出错)
26.数组初始化vector对象:
int arr[] = {1,2,3};
vector<int> ivec(std::begin(arr),std::end(arr))//这句语句后面的这个是待拷贝元素的下一个
27.多维数组:
int arr[3][4];//大小为3的数组,每个元素含有4个整数的数组
int(&row)[4] = aar[1];//把row绑定到aar的第二个4元素数组上
//类型别名简化多维数组的指针:
using int_array = int[4];
for(int_array *p = arr ; p != arr + 3 ; ++p){
for(int* q = *p ; q != *p +4 ; ++q){
cout<<*q<<"";
}
}
//28.
当一个对象做左值时,用的是对象的值,做右值时用的是对象的地址【指针应该是右值?】
int *p------decltype(*p)的结果是int&,decltype(&p)的结果是int**【decltype作用于左值一定是得到引用】
运算符%必须是两个整形,(m/n)*n + m%n = m m%(-n) = m%n (-m)%n = -(m%n)
逻辑非!赋值运算符= :从右往左看
j=++i;//得到递增后的值(尽量前++)
j=i++;//得到递增前的值 ++的优先级高于*
sizeof:返回类型所占的字节数 sizeof v; sizeof(int); sizeof *p;(右结合,相当于sizeof(p))
类型转换:int v = 3.14+1;//编译器会先3.14+1.0,然后4.14再丢得到4
char型和short型人称小整形,和其他类型运算时要先变整形
有符号数和无符号数运算,先转无符号
显示转换:static_cast(不含底层const都可以用,改类型) const_cast(只改底层const)
int j; double i = static_cast j;
任何非常量对象的地址都可以存入void
const char *p;char q = const_cast
const char *p;static_cast§;//正确const_cast§;//错误
30.
switch(){}里面的case注意别漏break;
break语句:终止与其最近的while, do while, for, switch
continue语句:终止当前迭代
31.自定义函数里的static i = 0;表示放在静态空间,函数结束不会销毁
32.传值的时候,形参不改变实参,例如传一个指针形参(即一个地址):
void reset(int *ip){
*ip = 0;//改变指针ip所指对象的值
ip = 0;//只改变ip的局部拷贝,实参未改变
}
33.传引用参数(即改本身),使用引用的好处:
1.避免拷贝,2.如果函数无需改变引用形参的值,最好将其声明为const,避免变量改常量的风险(尽量加)
//34.形参的顶层const会被忽略【注:顶层即指向变量本身,如const int i, int *const p】
void reset (int *ip){*ip = 0;}//自定义函数,【int *ip没有用const,所以是底层?】
const int ci = 0;
//调用函数reset(&ci);//错误,不能用指向const int对象的指针初始化int*
//35.数组形参(当指针)
string make_plus(ctr, const srting &word, const string &ending){return word;}//返回的是word的副本
const string &shorter*(const string &s1, const string &s2){return s2}//返回的是s2本身
36.不要返回局部对象的引用和指针
typedef int arrT[10];//arrT是一个类型别名,using arrT=int[10]
int(*func(int i))[10];//func(int i)表示调用func函数时需要一个int型实参
//(*func(int i))表示返回的是指针,我们可以进行解引用操作
//(*func(int i))[10]表示解引用后得到一个大小为10的数组
//int (*func(int i))[10]表示数组类型为int
int odd[] = {1,2,3,4,5};
decltype(odd) *arrPtr(int i){}//表示arrPtr函数返回的是指针,指针指向odd这样的数组
//局部变量不能作为实参
int i = 20;int j = 65; char k = '$';
string screen(int = i, int = j, char = k);
void main(){
k = '*';int i = 100;
window = screen();//此时k变成*了,而内部又定义的i是不能影响默认参数的,所以是执行screen(20 , 65 , *);
}
37.函数匹配等级(从高到低):
1.精确匹配;2.通过const转换实现的匹配; 3.通过类型提升实现的匹配(char ,unsigned char,short,unsigned short转成int)
4.通过算术类型转换的匹配 5.通过类类型转换实现的匹配
38.函数指针(指向函数的指针):bool lengthCompare(const string &, const string &);//声明一个可以指向该类型函数的指针,只要用指针替换函数名即可,如bool(*pf)(const string &, const string &);
39.在赋值语句使用时,直接pf = lengthCompare;或者pf = &lengthCompare;都行,返回的都是指针
在调用时可以直接用指针函数的指针调用该函数:如bool b=pf(“akl”,“pdq”);和bool b=(*pf)(“akl”,“pdq”);和bool b=lengthCompare(“akl”,“pdq”)三者是一样的
重载函数的指针必须精确匹配
函数类型的形参:不能定义函数类型的形参,但是形参可以是指向函数的指针
不能返回函数,但可以返回指向函数的指针(和函数类型的形参不一样,返回类型必须写成指针形式)
decltype(函数名):得到函数返回的类型
//40.cin是istream类型的对象,例如定义从输入流获取信息的函数:
istream &read(istream &is , Sales_data &item){
double price = 0;
is >> item.bookNo >> item.price;
return is;//返回cin ,这个函数是模拟cin的作用,平常的cin操作时最后也是返回cin本身
}
41.构造函数是定义在类里的一种特殊的成员函数,其名字必须和类的名字相同且没有返回值,构造函数可以重载为有输入形参
默认函数可以重载,但是要有一个无参的,如Sales_data() = default();//这个函数有效的前提是定义sales_data类时的成员赋了默认值
构造函数不能声明为const,如果存在类内的初始值,用它来初始化成员,否则默认初始化该成员,只有当类没有声明构造函数时编译器才会自动生成默认构造函数
在类里的函数后加const,表示一种承诺该函数不会修改数据,也表示这是一个底层const
mutable这个加在类型前的前缀表示即使对象是const的,也能修改
友元函数:在类里用friend声明一下非成员函数
42.不完整类型可以定义指向该类型的指针或引用,也可以作为函数声明中的参数或返回类型
一个类里用另一个类的函数的流程:
1.定义A类,声明abc函数,但不定义;
2.定义B类,包括对abc函数的友元声明;
3.定义abc函数,此时才能使用B类的成员。
43.委托构造函数,首先会有一个非委托构造函数,后面所有不同的输入形参,都会默认到用非委托的构造函数,而如果出现不能由非委托构造函数完成的,就找一个无参的委托构造函数,在通过赋值修改对象
44.explicit构造函数只能用于直接初始化,不能将explicit构造函数用于拷贝形式的初始化过程
字面值常量类:数据成员必须是字面值类型,类至少有一个constexpr构造函数,数据成员的类内初始值(必须是一条常量表达式,使用成员自己的constexpr构造函数),类必须使用析构函数的默认定义
constexpr构造函数体一般来说都是空的(因为constexpr函数只能有返回语句,而构造函数不能有返回语句,两者结合符合的只有空的)
45.初始化列表:1.效率高;2.有些类型的成员变量只能使用初始化列表(引用或者const);3.在继承时,子类的构造函数中初始化父类成员
浅拷贝:如果是含有指针的对象去赋给另一个对象,另一个对象的指针也会指向同一个地址,指向了同一块空间,会造成二次删除
普通对象既可以调用const成员对象,也可以调用普通对象
46.静态成员:所有对象共享同一份数据,在编译时分配内存,在类内声明类外初始化(类外声明时就不用加static了,但是要写 类名::),public里的可以通过在类外 类名:: 访问,privated里的只能类内访问,静态成员函数只能访问静态成员变量
子类从父类继承所有成员,除了构造函数、析构函数、赋值运算符重载函数,子类可以通过 父类名::函数 来调用父类的函数,子类可以用public和protect的,可以说protect是给子类函数专用的
没有virtual时,按对象定义时类型调用对应版本的函数,如果是virtual时,根据实际指向来调用对应版本的函数
如果一个类有子类,则父类的析构函数是虚析构函数(例如删除一个指向子类的父类指针,没有虚析构函数,只会delete属于父类的部分,子类的没删除,造成内存泄漏)
47.多态:父类的指针或引用有多种形态(换句话说是用父类指针或引用统一操作各种子类对象)
多态的代价【内存增加(32位的指针4个字节,每个对象增加一个表指针),运行效率慢10%-20% 】:如果一个类有虚函数,编译器会为每一个这个类的对象增加一个成员 虚函数表指针->虚函数表(里面是数组,存着虚函数指针)->虚函数
纯虚函数:虚函数只有声明,函数体=0 拥有纯虚函数的类是抽象类,在main函数中抽象类的对象是不能构造的
48.多重继承产生的问题:1.父类两个同名函数问题;2.孙子类有两份祖先类的成员
解决方法:
1.写明 父名::
2.虚继承,父类从祖先类虚继承,孙子类不用虚继承,但是写构造函数时要加祖先类的构造函数
C(int int1, int int2, int int3 ,int int4):R(int1), A(int2, int1), B(int3, int1), r(int4){}
多重继承引发的重复调用问题:将类自己的工作单独封装
49.float的0不是0,是-0.000001~+0.000001就算是0
50.异常处理,在可能出错的地方用if和throw,然后后面用到这部分内容时用try包括和catch(throw的内容类型/…(匹配任何throw)){}
51.定义一个通用函数/函数模板,template,当然模板也有默认参数,写成template,参数也可以多个