C++ 1day
类型增强
//hello world程序
#include<iostream>
using namespace std;
int main()
{
cout << "hello world" << endl; //相当于C的printf
return 0;
}
C++对类型的检测更为严格 如:malloc的使用
再比如const
#include <iostream>
using namespace std;
int main()
{
const int a = 100;
int *q = &a;
//这是不能通过编译,因为 &a的类型为 const int *,而 q 是int*类型不同C++报错
}
真正的枚举
#include<iosrteam>
using namespace std;
enum Season
{
Spring,Summer,Fall,Winter
};
int main()
{
Season s = Spring;
//这是可以的,也可以在Season前加上enum,但是右值不可以是除枚举类型Season以外的,则才是真正的枚举类型,所谓枚举类型就是先给出来在利用,只能利用举出来的
}
字符串类型
//字符串类型
int main()
{
string s; //遇到空格结束
cin >> s;
cout << s << endl;
return 0;
}
终端输入:abc abc
终端输出:abc
int main()
{
char s[1024];
cin >> s;
cout << s << endl;
return 0;}
终端输入:abc abc
终端输出:abc
函数同名不同参也就是重载
#include <iostream>
using namespace std;
void func(int a)
{
printf("%d\n", a);
}
void func(int* p)
{
printf("%d", p);
}
int main()
{
func(1); //类型本身就是 int
func((int*)1); //将类型转化为 Int *
func(NULL); //NULL是int类型的空
func(nullptr); //nullptr是指针类型的空
return 0;
}
终端输出:
1##
1
0##
0
运算符的重载也是函数的重载 <<
#include <iostream>
using namespace std;
struct COMP
{
float real;
float image;
};
COMP operator+(COMP one, COMP another)
{
one.real += another.real;
one.image += another.image;
return one;
}
int main()
{
COMP c1 = { 1,2 };
COMP c2 = { 3,4 };
COMP sum = c1 + c2; //返回一个结构体,结构体之间可以进行赋值运算
cout << sum.real << " " << sum.image << endl;
return 0;
}
//构造类型是不能比较的所以使用操作符的重载来达到目的
#include <iostream>
using namespace std;
typedef struct _pos
{
int x_; int y_;
}Pos;
bool operator == (Pos one, Pos another)
//operator 关键字,对操作符进行重载
{
if (one.x_ == another.x_ && one.y_ == another.y_)
return true;
else
return false;
}
int main()
{
Pos ps = { 1,2 };
Pos fd = { 3,4 };
if (ps == fd)
cout << "==" << endl;
else
cout << "!=" << endl;
return 0;
}
函数重载的规制:
- 函数名相同。
- 参数个数不同,参数的类型不同,参数顺序不同,均可构成重载。
- 返回值类型不同则不可以构成重载。(无法通过编译) //anbiguous (二义性)
C++的输入输出
#include <iostreeam>
using namespace;
int main()
{
int a,b,c;
cin>>a>>b>>c; //往a,b,c内输入数据
//cin表示流输入运算
cout<<"a = "<<a<<endl;cout<<"b = "<<b<<endl;cout<<"c = "<<c<<endl;
return 0;
}
关于进制的输出
#include <iostream>
using namespace std;
int main()
{
int data = 1234;
cout << data << endl;
cout << hex << data << endl;
cout << oct << data << endl;
cout << data << endl;
cout << dec << data << endl;
return 0;
}
终端输出
1234 第一次默认是十进制
4d2 输出十六进制
2322 输出八进制
2322 这种写法与第一种相同但是结果不同,是因上回输出了八进制,进行了保留
1234
域宽,对齐,填充
#include <iostream>
#include<iomanip> //manipulate 操纵
using namespace std;
int main()
{
float f = 1.234;
cout << f << endl;
cout << setw(10) << f << endl;// 将域宽设为10
cout << "***" << setw(10) << setiosflags(ios::left) << f << "**" << endl;
//设置对齐方式setiosflags(ios::left),默认是右对齐
cout << setw(10) << setfill('+') << f <<endl; setfill(字符)设置填充字符
return 0;
}
终端输出
1.234
1.234
***1.234 **
1.234+++++
默认参数
规则:
- 默认的顺序,是从右向左,不能跳跃。
- 函数声明和定义一体时,默认认参数在定义(声明)处。声明在前,定义在后,默认参 数在声明处。
- 默认值可以是常量,全局变量,或是一个函数
- 冲突 一个函数,不能既作重载,又作默认参数的函数。当你少写一个参数时,系统无法 确认是重载还是默认参数。(当实现功能,既能用默认参数又可以使用重载时,推荐使用默认参数)
#include<iostream>
using namespace std;
void func(int a){}
void func(int a,int b=2){}
int main()
{
func(1,5) //很明显是在调用第二个函数
func(4) //这就说不清楚了,可以理解为调用第一个函数,也可以理解为调用第二个函数,第二个参数的默认值为2
}
Reference &引用
定义:
变量名,本身是一段内存的引用,即别名(alias)。此处引入的引用,是为己有变量起一个 别名。
- 引用是一种声明关系,不开辟空间
- 引用必须要初始化,不能单独存在
- 与被别名的变量拥有相同的数据类型
- 声明关系一旦确立不可变更
- 可以对引用再次引用,也就是对一个变量可以进行建立多个引用,此时引用之间是平行关系
int a = 100;
int b = 20;
int &ra = a; //此时ra与a具有等价关系
int &rb = a; //c此时rb与ra是等价关系
int &ra = b; //违反了声明关系一旦确立不可变更
应用
c++很少使用独立变量的引用,如果使用某一个变量就直接使用他的二原名,没有必要使用他的别名。引用的目的就是取代指针传参,c++引入引用后,可以用引用解决的问题,就不用用指针解决了
引用从宏观上可以理解为,扩展了变量的作用域,传参后就像再本地解决问题一样。避免了传n级指针,解决n-1级问题,即平级解决问题
#include <iostream>
using namespace std;
void myswag(int &a, int &b)
{
int t = a;
a = b;
b = t;
}
int main()
{
int a = 1;
int b = 5;
myswag(a, b);
cout << "a = " << a << endl << "b = " << b << endl;
return 0;
}
终端输出
a = 5
b = 1
引用的提高
指针的引用(int *&)有,引用的指针(int& *)无
#include <iostream>
using namespace std;
void myswag(char *& p,char *&q)
{
char * t = p;
p = q;
q = t;
}
int main()
{
char* p = "china";
char* q = "amar";
cout << "p = " << p<< " q = " << q << endl;
myswag(p, q);
cout << "p = " << p << " q = " << q << endl;
return 0;
}
终端输出
p = china q = amar
p = amar q = china
引用的本质是对指针的包装,避免使用裸露的指针
int main()
{
int a;
int *p = &a; //指针p指向a
int & ra = a //对a进行引用
int *&pr = p; //对指针变量p进行引用,p是int*类型
int& * = &ra; //(不被允许)ra是int&类型,取地址后为int&*类型
//但是这种操作是不被允许的,引用就是对指针的包装,但是*又会解引用,不被允许
//对引用的指针类型 C++避免再次开封
}
指针的指针(int **)有,引用的引用(int &&)无,可以对引用再次取引用
int main() //指针的指针有
{
int a = 100;
int *p = &a;
int **pp = &p;
int ***p = &pp;
}
int main()
{
int a = 100;
int &pa = a; //对a进行引用
int &pb = pa; //对引用再次取引用
int& &pc = pa //(不被允许)引用的引用,pa是int&类型
}
指针数组有引用数组无
int main()
{
int a,b,c;
char*p[] = {&a,&b&c}; //指针数组
//证明:数组元素为 int*类型,p是首元素的地址为int**类型(指针的指针有)
int& p[] = {a,b,c}; //(不被允许)引用数组
//证明:数组元素为int&类型,p是首元素的地址为int&*类型(引用的指针无)
}
数组的引用
int main()
{
int arr[5] = {1,2,3,4,5};
//arr的类型为int*const类型,因为指针的指向是不可以发生改变的,但是指针指向的内容可以改变
int *const &par = arr; //对数组进行引用 (将数组名当成指针看)
for(int i=0;i<5;i++)
cout <<"par[i]= "<< par[i]<<endl;
}
终端输出
par[i] = 1 par[i] = 2 par[i] = 3 par[i] = 4 par[i] = 5
int main()
{
int arr= {1,2,3,4,5};
int(& par)[5] = arr; //将arr当成数组看
for(int i=0;i<5;i++)
cout <<"par[i]= "<< par[i]<<endl;
}
终端输出
par[i]= 1
par[i]= 2
par[i]= 3
par[i]= 4
par[i]= 5
day2
常引用
常引用的特征
const的本意,即不可修改。所以,const对象只能声明为const的引用,使其语义保持一致。non-const 对象,既可以声明const引用,也可以声明为no-const引用。声明const引用,则不可以通过const引用修改数据
int main()
{
//常量
int &ri = 3; //(不被允许)
const int &ri = 3; //(可以) 3为不可变常量
//类型不匹配对象
double d = 3.14;
int &rd = d; //(不被允许)
const int &rd = d; //(可以) 此时rd只具有读的属性
}
临时变量的常引用
临时对象:通常不可以取地址的对象,常见的临时值有常量,表达式,函数的返回值,类型不同的变量等
int foo()
{
int a = 100;
return a;
}
int main()
{
const int & cc = 100; //数字的引用
int a =3;int b = 5;
const int & pa = a+b; //表达式的引用
const int &pb = foo(); //函数返回值的引用+
double d = 10.12;
const int &fd = d;
}
new/delete堆内存操作
C语言提供了malloc.free两个系统函数完成对的内存操作。而C++提供两个关键字new和delete,这两个关键字是为类对象而设计的。这两个关键字重点用在类对象的申请与释放,申请的时候会调用构造器完成初始化,释放的时候会调用析构器完成内存的清理
new
语法:类型 + *,若是数组就是数组元素的类型
一维类型
struct Stu
{
char name[10];
char sex;
float score;
};
#include <iostream>
using namespace std;
int main()
{
int* p = new int; //通过new + 类型来分配大小为sizeof(类型)的堆内空间
//int* p =new int(10); //可以通过()来对值进行初始化初始化的对象是指针指向的内容
//int **p = new int *; //定义一个指向指针变量的指针变量
//Stu* ps = new Stu; //定义一个指向结构体类型的指针
//float *pp = new float[10]; //定义一个指向一维数组的指针变量
//char**ppp = new char*[10]; //定义一个指向指针数组的指针变量
cout << *p << endl;
}
终端输出:-842150451 //这说明new分配的空间没有初始化
二维类型
int main()
{
int(*p)[5] = new int[3][5]; //定义了一个指向二维数组的指针
//int[3][5]的类型是 int[5] 可以看为type[3]是数组,数组元素类型为int[5]
for(int i=0;i<3;i++)
{
for(int j=0;j<5;j++)
{
p[i][j] = i+j;
}
}
for(int i=0;i<3;i++)
{
for(int j=0;j<5;j++)
{
p[i][j] = i+j;
cout << p[i][j] << " ";
}
putchar(10);
}
}
终端输出
0 1 2 3 4
1 2 3 4 5
2 3 4 5 6
三位类型
int(*p)[3][5] = new int [2][3][5];//定义zh'z三位数组的指针
失败
new分配的空间不需要检测是否分配成功即语句 if(xxx == nullptr) exit(-1);cout<<“error”,因为系统会自动检测是否分配空间成功,若不成功则会抛异常,若不处理则会程序终止
用于检测失败的方式
int main()
{
double*pd[50];
try
{
for(int i=0;i<50;i++)
{
pd[i] = new double[5000000]
}
}catch(std::bad_alloc & e)
{
cout <<e.what()<<endl;
printf("xxxxxxxxxxxx");
}
}
一种简单的方式
#include <iostream>
using namespace std;
void newError()
{
cout << "new error" << endl;
exit(-1);
}
int main()
{
double* pd[50000];
set_new_handler(newError); //检测到喷配内存失败就调用newError
for (int i = 0; i < 50000; i++)
{
pd[i] = new double[99999999];
}
}
一种熟悉的方式
int main()
{
double *pd[50];
for(int i=0;i<50;i++)
{
pd[i] = new(nothrow)double[5000000]; //(nothrow)就将new不抛出异常相当于退化为了malloc
if(pd[i] == nullptr)
cout<<"new error"<<__FILE__<<__func__<<__LINE__<<endl;
exit(-1);
}
}
delete
int main()
{
int *p = new int; //摧毁一维的
delete p;
string* pi = new string("china");
delete pi;
int *pp = new int[10] //摧毁多维的
delete []pp;
int **ppp = new int *[10]
delete []ppp;
int (*ppp)[5] = new int[3][5];
delete[]ppp;
}
inline内联函数
引入:
int sqr(int x )
{
return (x * x);
}
int main()
{
int i = 0;
while (i < 5)
{
cout << SQR(i++)<<endl; //正常的函数调用
}
return 0;
}
终端输出
0
1
4
9
16
#define SQR(x) (x * x )
int main()
{
int i = 0;
while (i < 5)
{
cout << SQR(i++)<<endl; //调用宏函数,要用替换的思想
}
return 0;
}
终端输出
0
4
16
c语言中有宏函数的概念。宏函数的特点是内嵌到调用代码中去,避免了函数调用的开销。但是由于宏函数的处理发生在预处理阶段,缺失了语法检测和可能带来的语意差错,那么是否有一种函数即拥有普通函数的类型检查又可以内嵌,就有了inline 函数。
写法
就是在普通函数前加上inline
inline int sqr(int x )
{
return (x * x);
}
int main()
{
int i = 0;
while (i < 5)
{
cout << SQR(i++)<<endl; //正常的函数调用
}
return 0;
}
终端输出
0
1
4
9
16
作用:inline只是通过避免函数调用这一过程的额外开销,具体再编译时是否发生还要看编译器(建议函数),当此函数短小且频繁使用时,编译器才会采纳,以牺牲代码段的空间而换取时间的效率
Namespace命名空间
命名空间为了大型项目开发,而引入的一种避免命名冲突的一种机制。比如,在一个大型项目中,要用到多家软件开发商提供的类库。在事先没有约定的情况下,两套类库可能存在同名的函数或全局变量而产生冲突。项目越大用到的类库越多,开发人员越多,这种冲突就越明显。
使用(尽量用第一种,少用第二种,不用第三种)
#include<iosteam>
using namespace std;
namespace one //定义命名空间one
{
int x = 4;
}
namespace another //定义命名空间another
{
int x = 14;
}
int main() //第一种方式
{
cout << one::x <<endl;
cout <<another::x <<endl;
}
int main() //第二种
{
using one::a; //using释放命名空间one中的a,之后不能再释放
cout << a << endl;
//using another::a //(error)不可再次释放
}
int main() //第三种
{
using namespace one; //将命名空间全部释出来,之后不能再次释放
cout << a << endl;
//using nanespace another;//(error)
}
string初步
int main()
{
string s = "china"; //两种初始化方式
string s1("americal");
s = "americal" //可以被赋值
getline(cin,s); //向s内输入,可以输空格 etline
}
string的一些操作
int main()
{
string s1 = "great wall";
string s2 = "China";
cout << s1.size()<<endl;//求字符串长度
string s3 = s1 + s2; //字符串连接
if(s1 == s2) ... //字符串比较(< > !=都可以)
cout << s3 << endl;
cout<<s1[3]<<endl; //字符串类型支持下标
//(字符串对象).c_str用来对接C语言,将字符串对象转为har*类型
char buf[1024];
strcpy(buf,s1.c_str);//s
}
字符串与数字互转
#include <string>
int main()
{
s = "123456";
int a = stoi(s); //字符串转int型
int b = 123456;
string s1 = to_string(b); //整型转字符串类型
cout<<s1<<endl<<a;
}
cast类型转化
新类型的强制转化可以提供更好的控制强转过程,允许控制各种不同种类的强制转换。c++提供了四种转化 static_cast,reinterpret_cast,dynamic_cast,const_cast以满足不同需求,c++风格的强制好处是,他们能更清晰的表明他们要干什么
C++转换风格:xxx_cast (expression)
static_cast
语法格式:static_cast(expresion)
适用场景:再一个方向上可以作隐式转换,再另一个方向上就可以作静态转换
int main()
{
//双隐
double d;int i;
//d = i;i = d; //两个方向都是被允许的,称为双隐式转换
d = static_cast<double>(i);
i = static_cast<double>(d);
d = static_cast<double>(10/3);
//单隐
void *P;int* p;
//p = q是可以的,q = p不可以,void*类型可以被任意指针类型赋值
q = static_cast<int *>(p);
//再比如malloc函数
q = static_cast<int*>(mallloc(sizeof(10)*10));
}
reinterper_cast 重新解释
语法:reinterpret_cast(expression)
适用场景:通常为操作数的位模式提供较低层的重新解释,也就是将数据以二进制存在形式重新解释,在双方向上都不可以隐式转换的,则需要重新解释类型转换。
int *m;int n; //这两个类型不能互相转化
//要想互相转化,就要强制
m = reinterpret_cast<int *>(n);
n = reinterpret_cast<int>(m);
const_cast
使用格式:const_cast(expression)
适用场景:用来移除非 const 对象的引用或指针的常量性(const),使用const_cast去除 const 对于引用和指针的限定,通常是为了函数能够接受参数或返回值。注意:目标类型只能是指针或引用,const_cast是对const的一种补充
//const_cast只能作用于指针或引用,去const化
int main()
{
//对于引用
int a = 100;
const int& ra = a; //此时ra的值不可以发生改变
const_cast<int&>(ra) = 300; //对ra进行去const化,ra的值就可以发生改变
cout << ra << " " << a << " ";
//对于指针
const int* p = &a;
*const_cast<int*>(p) = 100;
cout<<a;
}
终端输出: 300 300 100
那么如果对象是const类型的呢?
int main()
{
const int a=100;
const int&ra = a;
const_cast<int&>(ra) = 200;
cout << a << " " << ra << endl;
}
终端输出: 100 200 //这不同于非const对象,但是ra与a的值就不同了,这只改变了ra的值但没有改变a的值
那么再看一个
struct Node
{
int a;
};
int main()
{
const Node st = { 40 };
const Node& pd = st;
const_cast<Node&>(pd).a = 100;
cout << pd.a << " " << st.a;
}
终端输出: 100 100 //很奇怪,这怎么又相等了
结论:const_cast只有对非const变量使用const的指针或引用是安全的,若原生就是const的,再对const_cast后的指针或引用执行写操作是未定义的