“C++ 专项查缺补漏"
[toc]
标准I/O库
cin、cout、cerr、clog是 I/O流类 预定义的4个流对象
cin———–istream流 类—–标准设备的输入
cout——–ostream流 类—–标准设备的输出
cerr——–ostream流 类—–标准出错信息输出—–无缓冲功能
clog——–ostream流 类—–标准出错信息输出—–具有缓冲功能
所有IO流对象都是不可以赋值、复制的,想要传参数,只能传指针或者引用
字符串、字符常量
字符串由双引号包裹,字符常量由单引号包裹。
运算符
- 递增运算符优先级高于解引用运算符:*p++实际上等价于*(p++),但是从输出的角度来讲,还是输出初始值的副本作为其求值的结果
- 成员访问运算符:
- 点运算符
- 箭头运算符:
ptr->s
等价于(*ptr).s
重载函数、重载运算符
允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载
- 函数重载:函数名称相同,形参不同;
函数屏蔽:
要注意,在局部空间,不可定义与需要调用的函数重名的变量,否则会发生函数屏蔽
函数匹配:
1.函数名相同 2.可行函数(形参个数相同,或有默认形参) 3.最佳匹配 4. 若有可行函数,但是没有最佳匹配,需要手动转型选择
实参类型转换:
1.类型提升 2.标准转换
#include<iostream>
using namespace std;
class printData{
public:
void print(int i){
cout<<"整数为:"<<i<<endl;
}
void print(double f){
cout<<"浮点数为:"<<f<<endl;
}
void print(char c[]){
cout<<"字符串为:"<<c<<endl;
}
};
int main(void){
printData pd;
pd.print(5);
pd.print(3.14);
char c[] = "Hello";
pd.print(c);
return 0;
}
//输出
//整数为:5
//浮点数为:3.14
//字符串为:Hello
- 运算符重载:
C++中预定义的运算符的操作对象只能是基本数据类型,但对于许多用户自定义类型(例如类),也需要类似的运算操作。这时就必须在C++中重新定义这些运算符,赋予已有运算符新的功能,使它能够用于特定类型执行特定的操作。
运算符函数的函数名是由关键字operator和其后要重载的运算符符号构成的,具有如下规则
(1) 除了类属关系运算符”.”、成员指针运算符”.*“、作用域运算符”::”、sizeof运算符和三目运算符”?:”以外,C++中的所有运算符都可以重载。
(2) 重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符。
(3) 运算符重载实质上是函数重载,因此编译程序对运算符重载的选择,遵循函数重载的选择原则。
(4) 重载之后的运算符==不能改变运算符的优先级和结合性==,也不能改变运算符操作数的个数及语法结构。
(5) 运算符重载不能改变该运算符用于内部类型对象的含义。它只能和用户自定义类型的对象一起使用,或者用于用户自定义类型的对象和内部类型的对象混合使用时。
(6) 运算符重载是针对新类型数据的实际需要对原有运算符进行的适当的改造,重载的功能应当与原有功能相类似,避免没有目的地使用重载运算符。
(7)重载运算符的函数不能有默认的参数,否则就改变了运算符的参数个数,与前面第3点相矛盾了;
(8)重载的运算符只能是用户自定义类型,否则就不是重载而是改变了现有的C++标准数据类型的运算符的规则了
(9)用户自定义类的运算符一般都必须重载后方可使用,但两个例外,运算符“=”和“&”不必用户重载;
(10)运算符重载可以通过成员函数的形式,也可是通过友元函数,非成员非友元的普通函数。
原码、反码、补码
以32位为例
int a = -12
a的原码 = 1000 0000 0000 0000 0000 0000 0000 1100
a的反码 = 1111 1111 1111 1111 1111 1111 1111 0011 # 符号位不变,其余位取反
a的补码 = a的反码 + 1 = 1111 1111 1111 1111 1111 1111 1111 0100
int b = 12
a的原码 = a的反码 = a的补码 = 0000 0000 0000 0000 0000 0000 0000 1100
语句
范围for语句
用于遍历容器或其他序列的所有元素 for(declaration:expression)
其中expression必须是一个序列,declaration是一个序列中元素可以转换的变量,一般用auto
类型说明符
引用
提供对其他对象的间接访问,相当于其他对象的==另外一个名字==,本身并非一个对象,且一旦定义了引用,就无法再绑定到其他对象
指针
指针是0,表示不指向任何对象;
指针是可以超出末端的;
int local_int = 0;
int *p = &local_int;
std::cout<<p<<std::endl; # 0x7ffee30166ec
std::cout<< *p << std::endl; # 0
// 指针指向了一个对象,用 * 来访问该对象
int ia[] ={1,2,3,4,5};
int *p = &ia[2];
int j = p[1] //j = 3,p括号里的值等于指针的加减运算
空指针
int *p = nullptr;
int *p = 0;
// 需要首先 #include cstdlib
int *p3 = NULL;
指向函数的指针
指针两边要加上小括号,返回值和形参类型都要完全一致
const限定符
定义变量的值不能改变;对const的引用必须也同样是一个常量来进行引用,但是允许一个常量引用一个非常量;
const对象默认为文件的局部变量,若想为全局变量 extern const int name = xxx
- 顶层const:指针本身是一个常量
- 底层const:指针所指对象是一个常量
常量指针:本身是一个指针,指向的常量,不需要初始化
指针常量:本身是一个常量指向的指针,需要初始化
赋值
赋值语句到底是改变了指针的值还是改变了指针所指对象的值
int i = 42;
int *p = 0;
pi = &i; //pi的值被改变,现pi指向了i
*pi = 0; //i的值被改变,指针pi没有改变
typedef
给原有类型定义新的名字,隐藏定义类型,突出使用功能,可以定义后再定义
枚举 enum
默认从0开始,也可以自己定义数值
enum days {Mon,Tue,};
int main(){
days t ;
t = Mon; // t = 0
return 0
}
编写头文件
头文件里写类的定义,外部变量声明,函数声明 extern double ival
命名空间
头文件里不可以使用using
标准库string
string输入一次只能读到空格前为止,如果想要读一整行,需要使用getline(cin,str_name)
函数
.size()
:计算字符串大小或者string::size_type
,后者可以保证字符串的大小一定可以装进所定义的变量中,
.empty()
:检测字符串是不是空的
C++中两个字符串字面值不可以加减,至少有一个是变量表示
**
ispunct()
:检查字符串中某一位是不是标点符号
isalnum()
:检查字符是不是字母或数字
isalpha()
:检查字符是不是字母
iscntrl()
:检查是不是控制字符
isdigit()
:检查是不是数字
islower()
:检查是不是小写字母
isspace()
:检查是不是空格
直接初始化与拷贝初始化:
直接初始化:
- string s1(s2)
- string s1(num,”xxx”)
- 也可以使用char的数组来初始化string
使用=
初始化一个变量实际上是拷贝初始化,使用小括号是直接初始化;
string类型的操作:
- .substring(pos,num):返回子串,超出范围不报错
- .append():在最后插入
- .replace(pos,num,”xxx”):替换,不要求个数相等的替换
- .find(“xxxx”):精确查找,找到返回下标,没找到返回
string::npos
, - .rfind():精确查找
- .find_first_of():在需要查找的字符串中查找提供的字符串中出现的任意第一个字符
- .find_first_not_of():
- .find_last_of():
- .find_last_not_if():
- .compare(s):返回值与0作比较,参数可以选择pos和num与被比较的字符串
标准库vector
是一个动态数组,比数组更灵活
push_back()
:向向量中传入数据
迭代器
iterator.begin()
:指向第一个元素
iterator++
:指向下一个元素
iterator.end()
:指向最后一个数的下一个(不存在)
迭代器实质上是一个指针,可以使用*
获得值,所有的容器都有迭代器
vector<>::iterator
:创建一个迭代器向量
标准库bitset
有自己的头文件**
bitset<32> a ; //a是大小为32位的二进制,全是0
bitset<16> b(0xffff); //1111 1111 1111 1111
且在进行下标记位的时候,是从右往左开始的,若碰见字符串,还要包括字符串最后的\0
,
string str("1111100");
bitset<16> a(str,2,3); // 0000 0000 0000 0110
.any()
:检查有没有1
.none()
:检查是不是一个1都没有
.count()
:统计一共有几个1
数组
长度是固定的;
数组的维数必须是一个常量,(编译时就能确定的);
全局的数组会自动初始化为0;
size_t
:专门用作创建数组的下标;
a2=a1
并不能实现将一个数组的值拷贝到另一个数组中
友元
私有属性也想让类外特殊的一些函数或类进行访问,就需要用到友元的技术
- 让一个函数或者类访问另一个类中的私有成员
- 关键字为
friend
友元函数
不是某个类的成员函数,不能调用私有的数据成员,在类里面public定义相同名字的友元friend type XXX
友元类
同友元函数,在类中定义该类为友元friend class XXX
public private protected
用户代码(类外)可以访问public成员而不能访问private成员;private成员只能由类成员(类内)和友元访问
struct & class区别
- struct默认防控属性是public的,而class默认的防控属性是private的
异常
传统处理方法
#include<iostream>
using namespace std;
int my_cp(const char *src_file,const char *dest_file)
{
FILE *in_file,*out_file;
in_file = fopen(src_file,"rb");
if(in_file==NULL)
return 1;
out_file = fopen(dest_file,"wb");
if(out_file==NULL)
return 2;
char rec[256];
size_t byte_in,byte_out;
while((byte_in = fread(rec,1,256,in_file))>0)
{
byte_out = fwrite(rec,1,byte_in,out_file);
if(byte_in != byte_out)
return 3;
}
fclose(in_file);
fclose(out_file);
return 0;
}
int main()
{
int result;
if((result = my_cp("/Users/tpcmi/Desktop/test.txt","/Users/tpcmi/Desktop/test2.txt"))!=0)
{
switch(result)
{
case 1:
printf("源文件出错\n");
break;
case 2:
printf("目标文件出错\n");
break;
case 3:
printf("拷贝出错\n");
break;
default:
printf("未知错误\n");
break;
}
}
printf("OK!");
return 0;
}
缺点:每执行一步都要去检查是否有错误
throw
将代码中return的地方,修改为throw,throw后面的值可以是字符串,数值,对象
try
{
fun();
}
catch(int e) //数值
{
printf("发生异常 %d",e);
}
catch(const char *e) //字符串
{
printf("发生异常 %s",e);
}
catch(fun1 e) //对象,构造函数
{
printf("xxxxxxx");
}
catch(...) //所有异常
{
printf("所有异常");
}
标准异常
- bad_alloc:在
中
#include<iostream>
#include<new>
using namespace std;
class Dog
{
public:
Dog()
{
parr = new int[1000000000]; //4Gb
}
private:
int *parr;
};
int main()
{
Dog *pDog;
try
{
for(int i = 1;i<1000000;i++)
{
pDog = new Dog();
cout<<i<<"th, successed!"<<endl;
}
}
catch(bad_alloc err)
{
cout<<"New 失败: "<<err.what()<<endl;
}
return 0;
}
- invalid_argument
- out_of_range:在
中
预处理器进行调试
预处理指令
#ifndef NDEBUG
cerr<<...<<endl;
#endif
预定义常量
-
__FILE__
:返回文件名 -
__LINE__
:返回行 -
__TIME__
:返回时间 -
__DATE__
:返回日期
assert断言
assert
:断言,用来调试,在中
函数
定义在函数里的是形参
调用时叫做实参,初始化形参
void 定义的函数表示没有返回值
局部对象
形参和函数体内部定义的变量统称为局部变量,普通局部变量又称为自动变量,仅在函数执行期间有效,局部静态变量,即在变量前加上static
,则该变量会持续到程序终止
int addOne()
{
static int str = 0; //无static时每次输出1,加上static后每次增加1;
return ++str;
}
int main()
{
for(int i = 0;i<10;i++)
cout<<addOne()<<endl;
return 0;
}
非引用形参(就是copy)
-
普通形参
- 非const形参:可以用const对象初始化非const对象
- const形参:传进来不能修改,可以由非const传入
-
指针形参
- 非const形参
- const形参
void Add(int x)
{
x += 1;
}
int a=1;
Add(a) // a = 1
调用函数前后a的值不变
void Add(int* x)
{
x += 1;
}
int a=1;
Add(&a) // a = 2
调用函数前后a的值改变
引用形参
可以真正的修改参数 ,而如果不需要修改传进去的参数,一般定义为const
void Add(int& x)
{
x += 1;
}
int a=1;
Add(a) // a = 2
调用后a的值
vector及容器参数传递
-
非引用型是复制,速度慢且占空间
-
引用型传递不用复制,速度快
-
传容器的迭代器为最优
数组形参
void print(int *x){ // x指向数组第一个元素,最优
}
void print(int x[]){ //同第一种方法
}
void print(int x[10]){ //同上一种方法,10可写可不写
}
// 多维数组
void print(int (*x)[n]){ //x指向一个数组的第一行,每一行有n个元素
}
函数声明
声明与定义可以写一起,最好是分开写,同时将声明写在头文件里,定义写在新建的源文件中,main
文件只需要调用声明了函数的头文件即可,一般默认参数也写在声明中,不可两个地方同时都有默认参数
内联函数
带有inline的函数,inline在函数定义前,习惯将内联函数定义在头文件中,
在编译时在调用点展开,主要针对短小的函数,不需要反复去调用这个函数,而一些大的函数不建议使用
类
成员函数
函数原型必须在类的内部,定义可以在外也可以在内,在外时Class::fuc(){}
构造函数
特殊的成员函数,初始化局部变量的值
- 与类同名
- 没有返回值
class Person
{
public:
Person():money(100),age(12){} //无参数构造函数,也叫默认构造函数,给变量赋初值
public:
int money;
int age;
}
自己没有写构造函数时,C++会自定义,如果是局部对象,通常自定义的值没有意义,而全局的对象或者静态的对象的定义才有可能不那么奇怪
复制构造函数 将传进来的class type
对象的参数一个个赋值
赋值操作符 有点类似于重载=
,赋值返回this
指针
通常情况下会自动生成,而当类中有指针成员时,得自己手动去写复制构造函数,因为自动生成的是将原指针的值直接复制给新指针,而将面临修改其中任一指针值都会改动另一个指针,如果其中一个指针的指被释放掉了,那另一个指针就会变成“迷途指针”
析构函数
用于释放资源
在构造函数前加上~
,通常会自动生成,但如果有指针一般要自己写析构函数,同时一定要写复制、赋值构造函数
this指针
通常情况下this指针可写可不写,
static 类成员
静态的类成员不属于任何一个对象,是属于类的,可以由类直接调用class::XXX
在类的成员前声明static
,那么这个类就的这个成员就仅有一个,修改类的所有对象中的任意一个这个成员,其他所有的对象的这个成员都会改变
深复制、浅复制、智能指针
通常情况下的自动生成的复制构造函数都是可以直接使用的,但是如果类中的参数存在指针,则在复制的时候,只是浅复制,复制后的两个对象的指针拥有相同的地址,此时需要通过手动构造复制构造函数,以实现深复制的目的,同时还需要写析构函数,在结束调用时,删除新分配的地址
智能指针解决了浅复制地址相同,和深复制需要创建新的空间的问题,