前言
博主linux说要写论文,必须先看论文同时看20篇左右的论文是可以对其中的一到两篇编程实现,并对其中的论文做的不足的地方做改进就可以写成一篇自己的论文,但是这样的话只能在比这个更低的一个期刊上发表,https://blog.csdn.net/linuxmake/article/details/8257988
使用说明
本文使用如下教程
C++手册https://www.php.cn/cplusplus/cpp-templates.html
C++ Primer Plus 第6版 中文版链接:https://pan.baidu.com/s/1bzEqcFJAFvl3AtqrESMcXg提取码:sj4t
软件VisualStudio2015,快捷键https://www.cnblogs.com/myFirstway/p/5163068.html Ctrl+k+d格式化代码,注释: 先CTRL+K,然后CTRL+C
取消注释: 先CTRL+K,然后CTRL+U,本工具已经把注释改为了ctrl+/,取消注释是Alt+C,代码提示是Alt+/
使用说明:本文一律采用先说原理和语法规则,然后在贴代码,本文所有代码都可以运行
目录
一,基本知识
1.1命名空间
//要std::是调用的命名空间,命名空间有两个人都叫张三的函数,没有办法区分是前面就要双通道std::,仅限在包含的头文件是#include <iostream>时,还可以使用using namespace std 声明过后,就不在使用std::,控制符endl就是输出后换行,cout显示消息的语句,cout和end1都是iostream中的定义的,
#include<iostream>
int main()
{
int a,b,sum;
using namespace std;
cout << "请输入两个数" << endl;
cin >> a;
cin >> b;
sum = a + b;
cout << a << "+" << b << "=" <<sum<< endl;
system("pause");
return 0;
}
1.命名空间
命名空间的语法格式如下
namespace 名称
{
//变量,常量,函数等的定义
}
使用域访问符''::''来访问命名空间的成员,自定义命名空间如下
namespace mystd
{
int a = 1;
int b = 2;
void f();
class per
{
};
}
使用using namespace mystd
using namespace mystd;
void f()
{
cout << "使用命名空间" << endl;
}
int main()
{
a = 2;
system("pause");
return 0;
}
不使用using namespace mystd
void mystd:: f()
{
}
int main()
{
mystd::a = 2;
system("pause");
return 0;
}
对类下面函数的引用,函数的void是在命名空间的前面void 命名空间 函数名()
#include<iostream>
using namespace std;//系统的命名空间
/*namespace定义*/
namespace mystd
{
int a = 1;
int b = 2;
void f();
class per
{ public:
void fun();
};
}
void mystd::per::fun()
{
cout << "name" << endl;
}
int main()
{
mystd::per zhangSan;//声明一个对象
zhangSan.fun();
system("pause");
return 0;
}
1.2 cin,cout
<<输出操作符左边通常是cout,>>输入操作符,通常是变量,这两个符号限制于iostream对象,可以cin>>a>>b,C++可以连续使用运算符int a,b;a=b=8;三元运算符a>b?a:b 如果是真就是a,假就是b;类和对象就像类型和变量一样,类里面可以定于成员变量和成员函数,构造函数和类名同名没有返回值,还有一个虚构函数类名前面加一个波浪线~person,构造函数首先被调用,
#include<iostream>
#include<string>
using namespace std;
class person //一般是把类的成员变量和成员函数定义在头文件中
{
private:
int pid;
string name;
int age;
public:
person();//构造函数
~person();//虚构函数
void setpid(int _pid);
int getpid();
void setname(string _name);
string getname();
void setage(int _age);
int getage();
//成员函数是要实例化的
};//;class的这个分号和函数的是有区别;号说明类还有一些对象
person::person()
{
cout << "构造函数" << endl;
}
person::~person()
{
cout << "虚构函数" << endl;
}
void person::setpid(int _pid)// person::setpid两个冒号表示person下面的setpid
{
this->pid = _pid;//this表示当前对象下的pid,当然this也是可以省略的
}
int person::getpid()
{
return this->pid;
}
void person::setname(string _name)
{
this->name = _name;
}
string person::getname()
{
return name;
}
void person::setage(int _age)
{
this->age = _age;
}
int person::getage()
{
return age;
}
int main()
{ /*
person per;//声明一个对象的时候就会调用构造函数,成员函数和构造函数的类型都是public
per.setpid(1);
per.setname("tom");
per.setage(20);
int pid = per.getpid();
string name = per.getname();
int age = per.getage();*/
//方法二通过指针的方式 //
person *per = new person();
per->setpid(1);
per->setname("ton");
per->setage(20);
int pid = per->getpid();//指针必须使用->,对象的形式可以使用.(点)操作符
string name = per->getname();
int age = per->getage();
cout << pid << "," << name << "," << age << endl;
delete per;//调用虚构函数
system("pause");
return 0;
}
成员函数,成员变量的定于写到头文件,函数的实现写到源文件,系统库包含使用<oo>,自定义的使用“oo.h”,
二,数据类型
整形有短整型整形长整形分别占用半个字节,一个字节,两个字节,字符cha表示一个字符,比如‘A,字符和整形可以相互转化,bool.可以用true和false赋值,也可以是用0(false)和非0表示,void表示无返回值,void*表示 任意的对象类型,
2.1.const的用法
字面常量,变量名称小写,单词分割可用下划线分隔或者首字母大写,函数体内部的变量必须进行初始化,类类的初始化,直接调用构造函数,在一个变量的前面声明const关键字,表示该变量不可被修改,即该变量是一个常量,const默认为局部变量,比如const int a=3;在另外一个.cpp中也要能使用的话必须定义为extern const int a=3;,在另外一个文件中也是这样引用extern const int a=3
int i = 100;//十进制
int k = 024;//八进制
int j = 0x16;//零x16是16进制
int i = 100;//变量的定义
int j(100);//另外一种变量的定义
extern int y;//变量的声明
2.2.引用
引用就是变量是一个别名,也就是说,它是某个已存在变量的另一个名字。定义引用的语法格式是在变量前面加&符号
,定义引用的时候必须赋初值,只是取名字的使用用&,之后r就是i了
int i = 100;//i叫张三
int &r = i;//r是张三的另外一个别名小张
//如果使用引用的方法
void swap1(int &a, int &b)
{
int temp;
temp = a;
a = b;
b = temp;
cout << "引用函数内" << a << "," << b << endl;
}
2.3typedef
typedef用于类型的定义,语法格式为,功能就是给数据类型取一个别名typedef int tao,以后tao就可代表int类型,还可以定义类类型和struct类型,struct的用法和class非常的像
struct Books
{
int book_id;
}book;//struct Books是结构体类型相当于int,book就是变量名,比如变量a
//不过book,可以是一个数组,当然在这里也可以先不取名在main函数再来定义
/*先不取变量名,在main中再取*/
struct Books
{
int book_id;
};//struct Books是结构体类型相当于int,book就是变量名,比如变量a
int main()
{
Books book;
book.book_id = 1;
return 0;
}
struct tao
{
int a = 1;
}book; //注意使用typedef时book才是它的别名,如果没有typedef就是结构体变量名
//比如水果有一个结构,蔬菜有一个结构,上面的tao,就像水果,book就像水果中苹果
//加了typedef的时候book才是上面的水果
2.4枚举
枚举就是有关联的常量,比如上下左右可以这样定义,但是关联性不强,我们使用枚举,语法格式如下enum 枚举类型名{枚举成员列表用逗号隔开};,enum,typedef等是和函数平齐的
int up= 1, down = 2,left=3,right=4;
#include<iostream>
using namespace std;
enum direction
{
up,//第一个枚举常量的值是0,也可以直接赋值up=100,down就是101
down,//第二个枚举常量的值是1
left1,
right1
};
void change(direction d)
{
switch (d)
{
case up:
cout << "up" << endl;
break;
case down:
cout << "down" << endl;
break;
case left1:
cout << "left1" << endl;
break;
case right1:
cout << "right1" << endl;
break;
default:
break;
}
}
int main()
{
change(up);//up就是常量0,常量一般会写为答谢
system("pause");
return 0;
}
2.5.自定义类型class
在设计类的时候我们先设计接口(interface),再设计类的实现(implementation);我们接口部分可以定义在头文件(.h)文件中,将类的实现定义在源文件中,并在源文件中包含头文件,一个头文件里面对应一个源,只需要在源文件实例化的时候包含头文件,如果还需要在包含main的源文件里面用类里面的方法的时候,我要也需要在包含main的源文件里面包含头文件,头文件里面可以定义多个类
class per
{
//变量定义为私有的
private:
int a = 1;
//函数定义为公共的
public:
void setpid(int a);
};//此处为什么是分号呢,因为我们可以直接用类型定义变量
class per1
{
//变量定义为私有的
private:
int a = 1;
//函数定义为公共的
public:
void setpid(int a);
}zhang,lisi;//此处为什么是分号呢,因为我们可以直接用类型定义变量
/*等同于*/
int main()
{
per zhansan,lisi;
system("pause");
return 0;
}
三,string和vector
3.1.string字符串
在c语言中使用字符数组来表示字符串,而在C++中提供string类来操作字符串,使用string类需要保护string类头文件和std命名空间
#include<string>
using namespace std;
字符串的定义和初始化
string s;//定义一个字符串
string s1(s);//用括号内的s来初始化
string s2("helloWorld");//利用""来初始化
3.2从键盘读入
1.读取的时候包含不包含空格可以使用cin
void s_write()
{
string s3;
cin >> s3;//遇到空格就截止
cout << s3 << endl;
}
2.读取的时候可读取整句化,空格也读入使用getline()
/*可以读取空格,输入bye的时候结束*/
void s_allwrite()
{
string s;
while (getline(cin, s))
{
cout << s << endl;
if (s=="bye")
{
break;
}
}
}
比如字符串定义为string s; s.empty()可以判断字符串是否为空,获取字符串的长度可以使用,s.size(),字符串的连接可以使用s+s1,可以通过索引获取某个字符,因为字符串本身也是一个数组,字符串是否相等可以使用"=="
void isempty()
{
string s="hello";
string s1 = "world";
cout << "是否是空字符"<<s.empty()<<endl;
cout <<"字符串的大小"<<s.size() << endl;//在字符串中的空格也算字符串的大小
char c = s[1];
cout << c << endl;
cout <<"拼接后的字符串"<< s + s1 << endl;
}
还有另外一些字符串操作函数和C类似,字符串实际上是使用 null 字符 '\0' 终止的一维字符数组
3.3vector向量
vector就是一个长度可变的数组即动态数组,是一个容器,容器类就是主要学习添加,删除,遍历,要使用向量也需要先包含进来,#include<vector>,首先创建vector,vector<int> v;//v是一个vector<int>对象
#include<iostream>
#include<string>
#include<vector>
using namespace std;//系统的命名空间
class person
{
};
int main()
{
//需要在<>内声明保存的类型
vector<int> v;//声明了一个vector v里面保存的是整形
vector<double> v1;
vector<string>v2;
vector<person> per;//声明为class类型的向量v
//使用push_back(类型值)添加到vector向量容器,上面的v就有点像我们的对象
//访问向量下面的函数方式同对象,操作遍历方式同数组
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
//遍历整个向量容器,可以使用for循环
int size = v.size();
for (int i=0;i< size;i++)
{
cout << v[i] << endl;
}
system("pause");
return 0;
}
3.3.1向量的访问,插入,删除
向量除了for还有另外一种方法,就是迭代器,迭代器实际就是指向数组的一个指针,iterator(迭代器)就是指向向量的一个指针,在这里再回顾一下指针,什么是指针,指针是一个变量的地址,int *ip; /* 一个整型的指针 */星号是用来指定一个变量是指针,所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值。*p++,+的优先级比*高,变量和指针一个是直接用变量名来访问变量的值,而使用指针的话,就需要先找到地址,然后通过一个特殊的取值运算符(*p),把变量的值取出来,为了和变量区别,我们叫它指针变量,//删除是v.erase(指针索引),v.erase(it+1),删除v中的第一个元素
int *p;//指针的定义
int a=1;//变量定义
p=&a;//指针的赋值
cout<<*a<<endl;//输出指针地址指向的值1
cout<<a<<endl;//输出指针地址
//iterator来遍历
vector<int>::const_iterator it = v.begin();//const_iterator是迭代器指针
//v.begin()是向量v的第一个地址
while (it!= v.end())
{
cout << *it++ << endl;//it是一个指针
}
cout << v.front() << endl;//输出第一个元素
cout << v.back() << endl;//输出最后一个元素
v.erase(it+1);
小结一下添加使用,v.push_back(),删除使用v.erase(it+1);遍历使用迭代器iterator,迭代器就是一个指向向量的指针,在某个位置插入元素使用insert(it+2,100),在这个位置放入100
四,数组和指针回顾
4.1数组
1,声明数组,需要说明类型和元素数量,数组里面的元素数量必须说明,只不过可以通过两种方式一个是比如a[10],另一种是在赋值的时候赋值完毕a[]={3,4,3},例如int a[10];2.使用for循环来访问数组里面的元素,3.使用带大括号的{}常量列表来初试话数组int a[2]={10,20,30};数组的名称代表数组的首地址,数组在c++这里增加了一个新东西,就是可以什么类类型的数组,数组不能直接赋值或者复制,需要使用for循环来操作,
class person
{
};
int main()
{
person perss[2];
person p1;
person p2;
perss[0] = p1;
system("pause");
return 0;
}
4.2指针
指针也是一种变量,是一种指向另外一种变量地址的变量,使用指针可以间接操作指向变量的对象
int main()
{
int a = 1;
int *p = &a;//*在这里表面p是一个指针,&表示取地址
cout << *p << endl;//这里的*表示取出地址所指向的值
//等效于
cout << a << endl;
//看a变量的地址
cout << &a << endl;
//看p得地址
cout << p << endl;
system("pause");
return 0;
}
指针可以指向某个对象的地址,可以指向NULL也就是0,可以把数组的名称指向指针,只有0常量可以用来得初试化指针,一个典型性的就是0,void*指针可以保存任何类型对象的地址,通常用来返回函数的返回值,当不确定的返回类型时,当形参的类型不确定的时候,也可以使用void*,使用new来分配内存,我们现在知道指针可以初始化为变量的地址,变量是在分配时有变量的内存,指针的真正用武之地在,在运行阶段分配未命名的内存以存储值,使用new运算符来操作,语法格式如下:类型名 *指针名=new 类型名,比如int *p=new int,在运行阶段以int类型为指针p分配一个地址,new和delete必须配套使用,delete只能用来释放new分配的内存.,同时指针I=NULL,new之后返回的是地址,delete之后应该重置指针为NULL;
int *p1 = new int;
delete p;//delete后面直接是指针名
void*的各种使用形式
#include<iostream>
#include<string>
#include<vector>
using namespace std;//系统的命名空间
class person
{
};
void test()
{
const int a = 0;
int *p = a;
int arry[] = { 1,2,3 };
int *p1 = &arry[0];//以后*p就是a变量的别名
}
void test3()
{
int i = 100;
void* p = &i;//void*可以保存任意的类型的地址
double d = 3.14;
void* q = &d;
}
//当返回类型不确定时void*还可以作为函数的返回值
void* test4()
{
int i = 100;
return &i;
}
//void*作为形参
void test5(void* a)
{
}
int main()
{
int a = 1;
int *p = &a;//*在这里表面p是一个指针,&表示取地址
int *p1 = new int;
delete p1;//delete后面直接是指针名
person *zhan = new person();
test5(zhan);
system("pause");
return 0;
}
4.3引用
引用就是变量的别名,引用总是指向某个对象,定义时没有初始化是错误的,指针定义的时候可以不初始化,指针就是一个变量,什么变量存放地址的变量,引用就是变量的别名,相对于给变量取了另外一个名字,指针在生命周期内除了可以指向初始化的地址,还可以指向其他地址,而引用不可以,就是指针沾花惹草,引用从一而终,定义也不一样指针int *a,而引用int &a=i;指针修改指向
int *ai = &i;
int *bj= &j;
//修改ai的指向为j
ai = &j;
//看看ai的值变了没有
cout << "原来ai的值" << i << "尝试更改后ai的值" << *ai << endl;
引用尝试修改指向,操作引用就等于再操作原变量,但是指针呢,我原来我指向的一个变量,如果你让我指向其他变量,那他和之前的变量就没有关系了,引用改变指向会影响到原来的指向
void test6()
{
int i = 100;
int j = 200;
int *ai = &i;
int *bj= &j;
//修改ai的指向为j
ai = &j;
//看看ai的值变了没有
cout << "原来ai的值" << i << "尝试更改后ai的值" << *ai << endl;
}
4.4二级指针
二级指针,指向指针的指针,首先弄清楚指针就是个变量,二级指针就是指向地址的地址,如果要取值的话需要需要取两次用**a取值,
void test6()
{
int i = 100;
int j = 200;
int *ai = &i;
int **bj=NULL;
bj = &ai;
cout << "原来ai的值" << i<<"ai的地址"<<ai << "bj的值" <<**bj << endl;
cout << "bj的地址" << bj << endl;
}
const和指针的关系,要看const是在哪里了,如const是在变量的前面,表示该变量是常量,如果要和指针结合使用,只能只能把const常量赋值给const指针,不能将const常量赋值给常规指针
//指针和常量都被禁止修改
const int pi = 3.14;
const int *p = π
//错误示范,不能指向,指向的话那常量的值岂不是可以修改了
const int pi = 3.14;
int *p = π
p不可以修改age的值但是p可以指向其他地址
int age = 39;
const int *p = &age;//p不可以修改age的值但是p可以指向其他地址
常量指针可以修改指向的值,但是指向不可以修改
int age = 39;
int *const p1 = &age;//p可以修改age的值,但是p不可以指向其他地址
*p1 = 40;
4.5.运算符
三元运算符i>j?1:2;是真结果就是1,假结果就是2,取余%;位运算与AND(&),或OR(|),异或XOR(^),NOT取反也叫非(~)取反之后的结果用补码输出就是反码加1;自增自减运算++i,--i,j++,j--,++前置先计算后赋值,++后置先赋值后计算,使用指针之后访问函数是
person per;
per.cau();
person *per1 = new person();
per1->cau();//使用->
#include<iostream>
using namespace std;
class fruit
{
public:
void print();
};
void fruit::print()
{
cout << "函数被执行" << endl;
}
int main()
{
fruit apple;
apple.print();
system("pause");
return 0;
}
类下面的函数声明要把类名放到void的后面,如上面代码,,类下面函数的声明要注意需要包含类名才能声明,注意变量的定义int a;变量的声明是extern int a;此变量在其它地方定义过,类的这个地方我们可以提前声明
switch (数字或字符常量)
{
case 1:
语句;
break;
default:
break;
}
do
{
}while(条件);
for(int i=0;i<10;i++)
{
}
五,函数
5.1函数的返回值类型和形参
可以是类类型,但是不能是数组类型
person test1()
{
person per;
return per;
}
非引用类型形参调用时,函数不会改变形参的值,它只是一个复制
void test(int a, int b)
{
cout << (a + b) << endl;
}
int main()
{
int i = 1;
int j = 2;
test(i, j);
system("pause");
}
引用的时候只是一个别名,会改变原来的值
void test(int &a, int &b)
{
cout << (a + b) << endl;
a = 10;
}
int main()
{
int i = 1;
int j = 2;
test(i,j);
cout << i << endl;
system("pause");
}
指正形参,修改形参指针不会修改实参指针,但是可以改变实参指向的对象,就是说两个指针指向了同一个地址,
void test(int *a, int *b)
{
cout << (*a + *b) << endl;
*a = 10;
}
int main()
{
int i = 1;
int j = 2;
test(&i,&j);
cout << i << endl;
system("pause");
}
但是地址可以重新指向
void test(int *a, int *b)
{
int c = 6;
a = &c;//现在a不指向i了修改a不会影响到i
cout << (*a + *b) << endl;
*a = 10;
}
int main()
{
int i = 1;
int j = 2;
test(&i,&j);
cout << i << endl;
system("pause");
}
如果形参是const类型,在函数中不能改变它的值
void test(const int a,const int b)
{
a = 100;//错误的,a是常量不能被改变
}
函数只能返回一个返回值,我们可以使用引用来,增加返回值的个数
int test( int &a,int &b,int &c)
{
c = a - b;
return a + b;
}
int main()
{
int i = 1;
int j = 2;
int back1 = 0;
int back2 = 0;
back2=test(i,j,back1);
cout << back1 <<'\t'<<back2<< endl;
system("pause");
}
对于大型对象,复制非常消耗时间和空间,可以使用引用来避免复制,如果不想让函数实参改变可以使用const
//只是把值传过来,不改变实参的值
void test(const int &a, const int &b)
{
}
形参int*&a,从右到左理解,a是一个引用,总体读为,指向指针的引用
数组形参,数组有两个特性一不能进行复制,所以不能当形参,二十数组名自动转换成指向第一个元素的指针,所以数组函数可以通过指针来处理,两种访问的方法一种是知道开始指针和结束指针,另外一种是在函数中传入数组名和数字的大小,
void test(int b, int *a)
{
int *p = a;
for (int i = 0;i < b;i++)
{
cout << *p++ << endl;
}
}
int main()
{
int a[3] = { 1,2,3 };
int b = size(a);
cout << "数组的大小" << b << endl;
test(b, a);
system("pause");
}
递归的返回也从最后一次递归,依次返回
int fact(int j)
{
if (j > 1)
{
int cc = j;
return fact(j - 1)*cc;
}
else
{
return 1;
}
}
int main()
{
int a = 5;
int b = 0;
b = fact(5);
cout << "值的大小" << b << endl;
system("pause");
}
5.2函数的声明
函数的声明,如果函数放在主函数的后面需要先声明
int fact(int j);
int main()
{
int a = 5;
int b = 0;
b = fact(5);
}
int fact(int j)
{
return 1;
}
我们一般是在头文件中声明函数原型,在源文件中定义函数,静态(static)局部变量,如果某个变量是静态局部变量,一旦变量初始化就会一直存在,直到程序执行结束,它本身还是一个局部变量,只是它的释放是在程序结束后,
5.3内联函数
内联函数的提出,我们大多数的表达式都尽量的写在函数里面,因为这是程序的模块化要求,但是函数调用的时候需要做很多工作,降低了程序执行的效率,为此我们提出了内联函数在原来函数的前面加上inline,内联函数就有点像直接调用里面的语句,就是相当于是顺序的,内联函数适合程序较短的片段,太大的程序不适合定义为内联函数,有点像goto.
inline int max(int a)
{
return 0;
}
类里面的函数函数原型必须声明在类里面,但是函数的实现可以是在类里面也可以在类的外面
5.3.1this指针
每一个成员函数(static成员函数除外),都有一个额外的,隐含的this指针,在调用函数是形成this,初试化为调用函数的对象地址
class person
{
private:
int custor;
public:
void setpid(int a1);
};
void person::setpid(int a1)//void person::setpid(person *this,int a)
{ //this表示调用当前函数的对象
//this->custor = a1;//这里的this等于person::custor=a1;
person::custor = a1;
cout << a1 << endl;
}
int main()
{
person per;
per.setpid(15);//当调用这个函数的对象是per,per的地址会赋值给this
int a = 5;
int b = 0;
}
5.4构造函数
构造函数是一个和类同名的函数,没有返回值,作用是初试化成员变量,不能加类型名,用普通函数和构造函数初试化类成员变量的区别
person::person()
{
//初试化成员变量,构造函数还可以重载
custor = 2;
}
void person::setpid(int a1)
{
person::custor = a1;
cout << a1 << endl;
}
class person
{
private:
int custor;
public:
person();
person(int pid) :custor(pid) {};//构造函数的初始化列表,
// 也就是直接通过列表初试化custor
void setpid(int a1);
};
5.5函数重载
函数重载是指函数名称相同,函数的参数个数或类型不同overloading,
void f();
void f(int a);
void f(string a);
//返回值不能构成函数的重载
//int f();就是错误的,因为这样调用的话会有矛盾
5.6.函数指针
函数指针就是指向函数的指针,而非指向对象的指针,像其他指针一样,函数指针也是指向特定类型的,函数的类型由返回值及参数列表确定,函数指针就是在函数名的前面加上*,并用括号括起来
int maxplus(int a, int b);
int maxplus(int a, int b)
{
cout << "最大值" <<(a+b)<< endl;
return 0;
}
int main()
{
int a = 1, b = 2;
int(*max)(int a, int b);//函数的名称就是函数的地址
max = maxplus;
(*max)(a, b);
system("pause");
}
函数指针也可以这样调用
int maxplus(int a, int b)
{
cout << "最大值" <<(a+b)<< endl;
return 0;
}
int main()
{
int a = 1, b = 2;
int(*max)(int a, int b);//函数的名称就是函数的地址
max = maxplus;
max(a, b);//指向完成之后,我就是你了
system("pause");
}
简言而之就是函数指针可以指向任意函数,指向完成之后,可以调用该函数的全部功能,也可以重新指向,用typedef简化函数指针定义
typedef int(*m)(int, int);//m以后就代表指针函数对象
m a = maxplus;
函数指针的形参可以实现回调,回调就是你调用我的时候我又去调用别人,
void maxplus(void(*min)(int a,int b))
{
min(1,2);
}
void mindown(int a,int b)
{
cout << "我被执行了" << endl;
}
int main()
{
maxplus(mindown);
system("pause");
}
5.7析构函数
析构函数就是用来释放构造函数声明的内存空间或打开的文件,在我们不用时必须释放内存并关闭文件,当对象不再使用时,,或者被delete时,编译器会自动调用析构函数,
class Person
{
private:
int *p;
ifstream in1;
public:
Person()
{
cout << "构造函数被执行" << endl;
p = new int(1024);
in1.open("source.txt", ios::in);
}
~Person()
{
cout << "析构函数被执行" << endl;
delete p;
in1.close();//关闭文件
}
};
void main()
{
//Person zhangsan;//这种方式虚构函数没有办法执行
//只有使用要释放对象才会调用析构函数
Person *per = new Person;
delete per;
system("pause");
}
六,文件操作
6.1标准库
对控制台的读写,我们前面使用cin,cout对象,头文件是iostream,对文件的读写,头文件是fstream,对字符串的读写头文件是sstream,条件状态,IO库提供一系列条件状态来标记这些状态
名称 | 说明 |
strm::badbit | 被破坏的流 |
strm::iostate | 条件状态值 |
strm::eofbit | 到达文件尾 |
s.rdstate(flag) | 设置状态 |
s.eof | 如果设置了strm::eofbit值,则该函数返回true |
3.缓冲区的管理
每一个Io对象管理一个缓冲区,用于存储程序读写的数据,下面几种状态会导致刷新缓冲区,1)程序正常结束,缓冲区已满2)用操作符显示刷新,例如endl
cout << "hello world" << endl;//使用endl,输出换行并刷新
cout << "hello worls" << flush;//只刷新
cout << "hello worls" << ends;//插入一个空字符并刷新
6.2文件的输入输出
1.有三个流用来处理文件ifstream,ofstream,fstream,2.打开文件有两种方式,一种是创建文件对象时使用构造打开文件,ofstream outfile("test.txt",ios::out),另一种方式是使用open函数打开文件,
ofstream ofile;
ofile.open("/tmp/test.txt",ios::out);
6.2.1写文件的步骤如下
1)包含头文件fstream,2)创建ofstream对象,3)将ofstream对象同一个文件关联起来,open() 函数是 fstream、ifstream 和 ofstream 对象的一个成员,4)就像使用cout那样使用ofstream对象
void write()
{
ofstream out1;
out1.open("testc1.txt", ios::out);//直接在源文件中添加该文件
cout << out1.fail();
if (!out1.fail())
{
out1 << "tom";
out1<< "mom";
out1 << "fom";
out1.flush();//刷新缓冲区
}
out1.close();//关闭文件,此种方式是使用创建一个对象来操作文件
}
void read()
{
}
int main()
{
write();
system("pause");
return 0;
}
6.2.2文件的读
void read()
{
//操作和写类似
ifstream in1;
in1.open("testc1.txt", ios::in);
//怎么来读呢
char c;
while (!in1.eof())//使用in1l.eof()来判断是否读到了文件尾
{
c = in1.get();//,每次读取一个字符
cout << c << endl;
}
cout << endl;
in1.close();
}
int main()
{
read();
//write();
system("pause");
return 0;
}
void read()
{
//操作和写类似
ifstream in1;
in1.open("testc1.txt", ios::in);
//怎么来读呢
char c[81];
in1.getline(c,81);//还可以使用这种方法来读
cout << c;
in1.close();
}
6.3字符串流
1.iostream库支持内存中的输入输出,只要将流与存储在程序中的string对象绑定起来即可2.绑定之后就可以使用iostream流读写这个string对象,标准库提供了三种类型的字符串流,istringstream,读string,ostringstream,写string,stringsstream,读写string
void test()
{
string line, word;
istringstream iss;
while (getline(cin, line))
{
istringstream iss(line);//输入的流和对象和line捆绑,以后操作iss就是操作line
//while (iss >> word)//将每一个字符串都传递给word;
//{
// cout << word << endl;
//}
cout << line << endl;//读出刚刚输入的东西
break;
}
}
七,容器
7.1.顺序容器
顺序容器包括vector,支持快速随机访问,类似数组,list支持快速插入和删除,deque(double-ended-queue),双端队列中间的效率低,适配器适配为另外一种特性stack栈后进后出,进入了死胡同,队列先进先出,priority_queue优先级队列,3.顺序容器的初始化
7.1.1容器声明
void test()
{
vector<int> id;
vector<string>name;
vector<person>trait;//容器的名称<容器的类型>对象名
list<int>l1;
list<string>s1;
list<person>pr;
deque<int>d1;
deque<person>pd1;
deque<string>sd1;
//使用迭代器来初始化,首先要指定迭代器的起始位置
}
7.2迭代器
像指针可以遍历数组一样,迭代器也可以用来遍历容器,2.迭代器常用的运算*ite返回迭代器所指向的元素的引用,3.vector和deque提供额外的运算,ite *n返回指向后n个元素的迭代器,4.迭代器的范围[first,last)前开后闭,返回第一个元素的位置和最后一个元素的位置,begin包含begin,end不包含end
void test()
{
vector<person>trait;//容器的名称<容器的类型>对象名
person zhansan; //类容器相当于把多个类放在容器里本质还是一个类
zhansan.pid = 1;
person lisi;
lisi.pid = 2;
trait.push_back(zhansan);
trait.push_back(lisi);
//获得迭代器
vector<person> ::iterator item = trait.begin();//注意iterator是vector下面的
item->printf();//访问类里面的成员,这里的item会变,开始的时候代表zhansan这个对象
//item+1之后就变为了lisi
while (item != trait.end())
{
item++->printf();
}
}
int main()
{
test();
system("pause");
return 0;
}
7.3顺序容器操作
顺序容器的操作1添加元素,2删除元素3,设置容器大小4获取容器元素,2)容器别名size_type无符号整形,容器长度iterator容器迭代器,const_iterator只读容器迭代器,value_type元素类型,reference元素引用,
void test()
{
int i;
vector<int> 容器别名 对象名
//比如 vector<int> size_type t;
vector<int> reference ref=i;
}
begiin和end成员,begin()指向第一个元素的迭代器end()指向最后一个元素的迭代器,rbegin()指向最后一个元素的迭代器,rend()指向第一个元素的迭代器,容器别名都是vector下面的
顺序容器的操作
vector可以使用push_back(元素)在容器末尾添加一个元素,list和deque可以使用push_front(元素)在容器前段添加一个元素,
void test()
{
list<int> score;
score.push_back(20);
score.push_front(13);
}
insert(p,e)在指定位置插入元素,insert(P,n,e)在指定位置插入n个元素,容器大小的操作size()返回容器大小max_size()返回容器最多可以容纳的元素个数empty()判断容器是否为空,resize(n),调制容器的大小为n,resize(n,2)扩充为n大小,新扩充的元素赋值为2,
7.4容器访问元素
访问元素:front()返回第一个元素的引用back()返回最后一个元素的应[n]像数组引用那样仅适合vector和deque,at(n),n索引位置的引用仅适合vector和deque()
void test()
{
vector <int> v;
v.push_back(1);
v.push_back(2);
v.push_back(13);
cout << v[1] << endl;
cout << v.at(2) << endl;
}
void test()
{
list<int> score;
score.push_back(20);
score.push_front(13);
list <int>::iterator itl = score.begin();
while (itl!=score.end())
{
cout << *itl++<<endl;
}
}
删除元素erase(p)删除迭代器p所指向的元素,返回一个迭代器,指向删除之后的元素erase(p,p1)删除p和P1迭代器之间的元素,返回一个迭代器,指向删除之后的元素clear()删除所有元素,pop_back()删除最后一个元素pop_front()删除第一个元素,赋值和交换c1=c2删除c1中所有元素,将c2中的元素赋值到c1,c1.swap(c2),c1和c2交换元素,c.assign(b,e)使用b和e之间的迭代器,重新设置c中的元素,9.vector的内存分配策略,vector中的元素是连续存储的,可以通过索引来访问,
7.5容器的选择
vector和deque可以对元素进行快速随机访问,但是插入和删除效率较低,因为需要移动其它元素,list在插入和删除上比较快,因为用指针,但是访问比较慢,因为元素不连续,如果不是在中间位置,而是在首位置插入或删除元素,则使用deque
7.6介绍容器后的字符串
构造字符串string s(s2,pos2,len2),截取s2字符串中从pos2开始len长度的字符串,string s(s1,0,2)截取从0开始到2的字符串,string(s2,3),截取s2中0到3长度的字符串,使用string可以就像使用vector容器一样,以容器的方式修改string,insert(p,t)在p的位置插入t值,insert(p,n,t),在p的位置插入n个t值,erase(b,e)删除b,e区间的值()里面传入的多数四指针值,字符串的操作一定包含头文件
void test()
{
string s2 = "hello";
string::iterator it = s2.begin();
s2.insert(it, 'a');
cout << s2 << endl;
s2.assign(s2.begin(), s2.end());
cout << s2 << endl;
s2.erase(it + 1);
cout << s2 << endl;
}
子串,追加和替换s.substr(pos,n),s.append(value),s.replace(pos,len,args),注意这些这些都是方法需要对象点方法才能调用,字符串查找s.find(args),查找第一个出现的位置find_first_of(args),查找最后一个出现的位置find_last_of(args),找到返回0,找不到返回非零
void test()
{
string s2 = "hello",s;
s = s2.append("ad");
cout << s << endl;
s = s2.substr(5);
cout << s << endl;
}
7.7适配器
适配器,适配器这个词源于电器,就是把220V的电压转为5给手机充电的设备,在C++中是一种设计模式,即在原有的接口上添加新的接口,使其适合另一种操作,2,顺序容器有三种适配器stack(栈)后进先出(LIFO),queue先进先出(FIFO),优先队列priority_queue;适配器的类型和声明,声明一个空的适配器a;a(c)创建一个内容为c的适配器,stack,queue是基于deque实现的,所以不能使用vector,list构造,而priority_queue在vector容器上实现,
void test()
{
stack<int> a;//注意记得包含进头文件stack,queue,deque
queue<int>b;
priority_queue <int> c;
deque<int> d;
d.push_back(1);
d.push_back(2);
//用双端队列初始化栈
stack<int> s1(d);
}
7.8栈
栈的操作empty(),size(),pop()删除栈顶元素,top()返回栈顶元素,push(),栈里面元素的访问
void test()
{
stack<int> a;//注意记得包含进头文件stack,queue,deque
a.push(1);
a.push(2);
a.push(3);
int flag=a.size();
//a.pop();
int *it =&a.top();
while (flag>0)
{
cout << *it << endl;
it--;
flag--;
}
}
7.9队列
队列和优先队列的操作方法q.empty(),q.size(),q.pop删除头,q.front(),q.back()最后一个元素的值.q.top(),q.push();
7.10.关联容器
关联容器,前面的是索引容器或顺序容器,而关联容器是通过键(key)来访问,每一个值都关联一个key,通过key访问,关联容器包括map,set,multimap和multiset,map是key-value的键值,通过key获得value,set只有一个key,并且是唯一的,如果key需要重复,可以使用multimap或multiset,6,pair类型 pair包含两个数值,包含在utility头文件中,常有的操作有pair<键的类型,值的类型>容器函数名,获取键可以使用P1.first获取,键值可使用P1.second获取
pair的初始化,还可以使用make_pair()来初试化,p1=make_pair("tom","three")
void test()
{
pair<string, string> p1("1", "one");
pair<string, int>p2("two", 1);//后面直接用值初试化了,一般来说名称后面有括号,都是初始化
//一个键对应着一个数组或者向量
pair<string, vector<int>>p3;
}
map,是一个key-value的形式,可以理解为数组,key相当于数组下标,value就是数组里面的值,map里面元素的访问
void test()
{
map<string, string>p;//要把map包进来
//像数组一样初试化map
p[ "1" ] = "zhansan";
p["2"] = "lisi";
p["3"] = "wangwu";
cout << p.begin()->first << "," << p.begin()->second << endl;//first是键second是值
//这里有一个指针的++在前和在后的问题
//cout <<( p.begin()++)->first << "," << (p.begin()++)->second<< endl;
cout <<(++ p.begin())->first << "," << (++p.begin())->second<< endl;
//当然还可以使用数组类型的方式访问
cout << p["1"] << endl;
}
因为pair本身就是一个键值对,我们可以先弄好pair再使用insert,插入到map,map的索引和数组的区别是,数组的下标只能是数字,而map不仅可以是数字还可以是字符串,因为pair本身就是一个键值对,我们可以先弄好pair再使用map的insert(),插入到map,insert默认在最后插入,还可以使用insert(pos,pair键值名)
void test()
{
map<string, string>p;//要把map包进来
//像数组一样初试化map
p[ "1" ] = "zhansan";
p["2"] = "lisi";
p["3"] = "wangwu";
//因为pair本身就是一个键值对,我们可以先弄好pair再使用map的insert(),插入到map
pair<string, string>p1("4", "lihua");
pair<string, string>p2=make_pair("5", "lihuabro");
p.insert(p1);
p.insert(p2);
cout << p["5"] << endl;
}
map里面元素的查找通过map的find方法,p.find(键值),返回的是键值所对应的指针,删除是通过p.erase(“1”),遍历通过iterator迭代器,
void test()
{
map<string, string>p;//要把map包进来
//像数组一样初试化map
p[ "1" ] = "zhansan";
p["2"] = "lisi";
p["3"] = "wangwu";
map<string, string>::iterator it = p.begin();
while (it!=p.end())
{
cout << it->first << it->second << endl;
it++;
}
}
3.set类型map是键值对,而set只有key,和vector区别是set中的对象不能重复,插入也是通过insert,s.insert(键值),查找通过s.find(键值)返回值是迭代器的位置,也就是一个指针,删除也是通过s.erase();
void test()
{
//我们通过vector来初试化set
vector<int> v;
v.push_back(1);
v.push_back(1);
v.push_back(2);
v.push_back(3);
//当然也是要把set包含进来
set<int>s(v.begin(), v.end());
cout << s.size() << endl;
//set可以向操作vector那样来操作
set<int>::iterator it = s.begin();
while (it!=s.end())
{
cout << *it++ << endl;
}
it = s.find(3);
cout << *it << endl;
s.erase(2);
}
八,算法
1.算法部分STL算法部分主要由头文件<algorithm>算法函数包含在其中,数值算法包含在<numeric>中,<functional>中有一些模板类,用来声明函数对象2,STL算法大致分为四类,1)非修正算法不修改容器内容的算法,2)修正算法:可以修改容器内容的算法,3)排序算法:对容器内容进行排序,4)数值算法:对内容进行数值计算,算法紧密依赖迭代器
8.1非修正算法
//介绍几个非修正算法,计算重复元素使用count(起始位置,中值位置,元素)得到的是重复元素个数2,查找使用find(起始位置,终止位置,查找元素),返回的是查找元素的位置,通过解引用得到值3.查找子序列最后出现的位置find_end(起始位置,终止位置,起始序列位置,终止序列位置)
for_each(起始位置,终止位置,函数名)函数名就是一个地址会调用函数5.搜索相邻重复的元素使用adjacent_find(起始位置,终止位置),返回的是一个指针
void test()
{
vector<int>v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
int cnt = count(v.begin(), v.end(),2);
cout << "重复个数" << cnt << endl;
vector<int>::iterator it = find(v.begin(), v.end(), 3);
cout << "找到的值" << *it << endl;
}
void test()
{
vector<int>v;
v.push_back(1);
v.push_back(1);
v.push_back(3);
v.push_back(3);
vector<int>::iterator it1=adjacent_find(v.begin(), v.end());
cout << *it1++ << endl;
cout << *it1 << endl;
}
foreach(传递工,传递对象),会把list里面的每一个对象依次传递给传递工,我们可以对传递工进行操作,
void test()
{
vector<int>v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
for_each(v.begin(),v.end(),print1);//把每个值传入这个函数中执行
//等效于
for (auto it = v.begin(); it != v.end(); ++it)//这里auto和iterator
{
print1(*it);
}
}
8.2修正算法
修正算法:修正算法会修改集合中的元素1,fill(起始填充地址,中指填充地址,填充元素),generate(起始位置,终止位置,函数返回值就是调用有函数返回的函数名)3.洗牌打乱里面的元素,random_shuffle(起始位置,终止位置)4.颠倒元素的次序reverse(起始位置,终止位置)5.分割使用partition(起始位置,终止位置,分割条件),首先处理的这个容器是排好序的,
小结一下,这里的算法好多和指针相关,要么传入的是指针,要么返回值指针,如果读软件封装好的文件pr,一般代表指针
void print1(int a)
{
cout << "我被执行了" <<a<< endl;
}
int fun(int i)
{
return i<2;
}
void test()
{
vector<int>v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>::iterator it = partition(v.begin(), v.end(),fun);
while (it!=v.end())
{
cout << *it++ << endl;
}
}
对数据进行计算就叫数值算法比如累加,数值算法要记得包含头文件numeric,和algorithm,累加求和使用accumulate(起始位置,终止位置,最后加上的值),内积就是两个向量的元素对应相乘,使用inne_product(第一个向量的起始位置,第二个向量的终止位置,第二个向量的起始,最后再加上的值)
void test()
{
vector<int>v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
int sum1 = inner_product(v.begin(), v.end(), v1.begin(),0);
//最后一个元素值是结果加0
cout << sum1 << endl;
}
8.3排序,查找算法
排序使用sort(起始位置,终止位置),最大值max(),查找最大值所在的位使用迭代器
vector<int>::iterator it = max_element(v.begin(), v.end());
cout <<*it<< endl;
查找使用中间查找(这种算法要排好序才能查找)使用binary_search(起始位置,终止位子,查找的元素),返回是一个bool类型
类:类中当然成员函数或成员变量在java中也叫方法和属性,有两个函数注意一个是构造函数一个是虚构函数构造函数主要用来初试化成员变量,虚构函数里面释放我们动态创建的对象
class person //一般是把类的成员变量和成员函数定义在头文件中
{ private:
int a, b, c;
person();
//构造函数重构
person(int a, int b, int c);
~person();//虚构函数里面释放我们动态创建的对象,
};
成员函数的声明必须在类的内部,但是函数的定义可以是在类内部也可以是类外部,内部定义默认是inline的,可以写inline也可以不写,构造函数和成员函数都可以重构,类的设计者->接口实现,类的使用者->使用接口
class person //一般是把类的成员变量和成员函数定义在头文件中
{ void fun() { cout << "我被执行" << endl; }
//等效于
inline void fun() { cout << "我被执行" << endl; }
};
类类型就像int一样,是一种数据类型,类的访问通过点运算符,或者new出一个对象的地址,通过->来访问,new出来的对象要delete并重新指向NULL或0
int main()
{
//类中的方法要对象才可以调用
Person zhangsan;
zhangsan.fun();
Person *p = new Person;
p->fun();
delete p;
p = 0;
system("pause");
return 0;
}
this指针
成员函数有一个隐含的形参指针this,它指向该类对象的一个指针,成员函数不能定义this形参,它由编译器自行定义,this可以看做是等于类名,this的使用时机,区分形参和成员数据
void set(int age)
{
//比如成员变量里面也有一个age,我们用形参赋值时
this->age=age;
}
//返回对象本身
Person *get(){return this}
//还有一种就是返回对象的引用
Person&get(){return *this}
类里面的成员变量就是给成员函数使用的,在要在类里面的函数也是可以相互调用的,静态成员函数可以直接通过类名来调用,而不用声明一个对象,再通过对象要调用
class A
{
public:
int a;
static void f1();
};
void A::f1()
{
cout << "A里面的函数" << endl;
}
int main()
{
A::f1();
system("pause");
return 0;
}
成员函数的相互调用
class A
{
public:
int a;
static void f1();
void f2();
};
void A::f1()
{
cout << "A里面的函数" << endl;
}
void A::f2()
{
//在f2里面直接调用f1,成员函数可以相互调用,静态成员函数除外
f1();
}
构造函数构造函数(没有返回值和类同名,声明对象后会自动调用),默认构造函数没有参数,可以用初始化列表来初始化成员变量,格式就是构造函数:初始化数据
class Mydata
{
private:
int year;
int month;
int day;
public:
Mydata() :year(2014), month(10), day(30)//这就是初试化列表给成员变量赋值
{
cout << "构造函数被执行" << endl;
};
};
//实现构造函数,上面已经用列表初始化过了,这里就不能再写构造函数了
//Mydata::Mydata()
//{
// cout << year << month << day << endl;
//}
构造函数可以重载
class Mydata
{
private:
int year;
int month;
int day;
public:
Mydata() :year(2014), month(10), day(30)//这就是初试化列表给成员变量赋值
{
cout << "构造函数被执行" << endl;
};
Mydata(int year);
};
Mydata::Mydata(int year)
{
cout << "重载函数" << year<<endl;
}
九,友元
友元:正常情况下类和类之间的私有成员变量是不可以相互访问的但是如果在一个类中声明了该类是友元(friend),则该类可以调用这个类的私有成员变量,成员,friend只能出现在类的定义内部,友元函数的声明friend void A::f(a,b),在用到的时候说明就好,A里面私有的东西没法用,除非A自己说B是我的朋友才可以用,有东西有主动权,类因为东西多比较谨慎
#include<iostream>
using namespace std;
class Human
{
private:
string name;
};
void marry(Human human1, Human human2)
{
cout << human1.name << endl;//普通函数中访问一个类的私有成员是非法的
}
9.1友元类
现在没有办法访问私有的部分,怎只需要在该类中声明该函数是友元函数,正常函数的声明前加一个friend,既然是函数那就声明在public哪里即可,有源关系不可以被继承和传递,声明友元类是friend class MarryPlace;这这样声明
class Human
{
friend class MarryPlace;
public:
// friend void marry(Human human1, Human human2);
void setname(string name)
{
this->name = name;
}
private:
string name="lihong";
};
class MarryPlace
{
public:
void marry(Human human1, Human human2)
{
//cout<<human1.name << endl;//普通函数中访问一个类的私有成员是非法的
//cout << human2.name << endl;//普通函数中访问一个类的私有成员是非法的
human1.name = "jia";
cout << human1.name << endl;
}
void prtf();
};
void MarryPlace::prtf()
{
cout << "我是Marry_place下面的" << endl;
Human tao;
tao.name = "";
}
void main()
{
Human tao;
MarryPlace li;
system("pause");
}
1,友元类 2. 友元函数
9.2友元函数
class Human
{
public:
friend void marry(Human human1, Human human2);
void setname(string name)
{
this->name = name;
}
private:
string name="lihong";
};
void marry(Human human1, Human human2)
{
//cout<<human1.name << endl;//普通函数中访问一个类的私有成员是非法的
//cout << human2.name << endl;//普通函数中访问一个类的私有成员是非法的
human1.name = "jia";
cout << human1.name << endl;
}
void main()
{
Human tao;
system("pause");
}
复制构造函数构造函数的复制,特殊在具有当前类型的一个引用
class Berson
{
public:
Berson() {}
//构造函数的复制,特殊在具有当前类型的一个引用
Berson(Berson &per)
{
cout << "我是复制构造函数" << endl;
}
};
复制构造函数的使用,当定义一个新对象,并用一个同类型对象初始化时将调用复制构造函数,3.当该类类型的对象传递给函数或函数返回,将式的调用复制构造函数
class Berson
{
public:
Berson() {}
//构造函数的复制,特殊在具有当前类型的一个引用
Berson(Berson &per)
{
cout << "我是复制构造函数" << endl;
}
};
void main()
{
Berson zhangsan;
Berson lisi(zhangsan);//这表示用张三来初试化李四对象,这个时候就是调用的复制构造函数
system("pause");
}
2.复制构造函数使用的时机1,根据通同类型对象初始化另外一个对象,2,复制一个对象,将它作为实参传递给一个函数,3从函数返回是复制一个对象,总的来说就是涉及到对象的复制都会调用,复制构造函数,
class Berson
{
public:
Berson() {}
//构造函数的复制,特殊在具有当前类型的一个引用
Berson(Berson &per)
{
cout << "我是复制构造函数" << endl;
}
void print()
{
cout << "我是Berson的函数" << endl;
}
void printf(Berson wanwu)
{
wanwu.print();
}
};
void main()
{
Berson zhangsan;
Berson lisi;
zhangsan.printf(lisi);
system("pause");
}
返回值是一个对象时
class Person
{
public:
Person get()
{
return *this;
}
}
默认复制构造函数,像默认构造函数一样,没有明显的自己定义,系统将默认提供一个
十,重载
10.1重载的例子
重载操作符是一些函数,其名字为operator,后面跟要重载的操作符名称,例如operator =;和普通的函数一样,操作符函数有一返回值和一个形参列表,形参数量和操作符的操作数相同,例如=号有两个操作数,3如果操作数是一个成员,则默认第一个参数是this,赋值操作符有两个形参,第一个形参是左操作符,第二个形参是右操作符,
例如下面是=号的一个重载
class Person
{
private:
int pid;
string name;
int age;
public:
Person() {}
Person(int pid, string name, int age) :pid(pid), name(name), age(age) {}
Person& operator=(const Person&per) //操作符的重载返回的是 Person的引用
//注意这里的Person的引用是Person&
{
this->pid = per.pid;
this->name = per.name;
this->age = per.age;
return *this;
}
friend void print(Person per);
};
void print(Person per)
{
cout <<per.pid<< per.name<<per.age << endl;
}
void main()
{
Person zhangsan;
Person lisi(1,"tom",12);
zhangsan = lisi;
print(lisi);
print(zhangsan);
system("pause");
}
重载操作符可以定义为成员函数(第一个参数默认是this)或非成员函数(如果是这种需要在类里面声明是友元),使用重载操作符,可以像函数调用一样使用,也可以像普通操作符一样使用,
class Person
{
friend Person& operator+(Person& p1, Person& p2);
private:
int pid;
string name;
int age;
public:
Person(int pid, string name, int age) :pid(pid), name(name), age(age)
{
cout << "构造函数被执行" << endl;
}
~Person() {}
void print()
{
cout << age << "," << name << endl;
}
};
Person& operator+(Person& p1, Person& p2)//此时的函数名就是oprator+
//注意这里是传递的p1的引用
{
p1.pid += p2.pid;
p1.name += p2.name;
p1.age += p2.age;
p1.print();
return p1;
}
void main()
{
Person zhangsan(1, "tom", 12);
Person lisi(1, "tim", 13);
//Person son= zhangsan+lisi;
Person son = operator+(zhangsan, lisi);
son.print();
system("pause");
}
注意当作为成员函数的时候有一参数是this,只要创建对象调用成员函数的方法,就会默认传递,
class Person
{
private:
int pid;
string name;
int age;
public:
Person(int pid, string name, int age) :pid(pid), name(name), age(age)
{
cout << "构造函数被执行" << endl;
}
~Person() {}
void print()
{
cout << age << "," << name << endl;
}
Person& operator+( Person& p2)//此时的函数名就是oprator+
//注意这里是传递的p1的引用
{
this->pid += p2.pid;
this->name += p2.name;
this->age += p2.age;
this->print();
return *this;
}
};
void main()
{
Person zhangsan(1, "tom", 12);
Person lisi(1, "tim", 13);
//Person son= zhangsan+lisi;
zhangsan.operator+(lisi);
system("pause");
}
输入输出重载之后该操作符其实就代表完成函数特定的功能
class Person
{
friend ostream& operator<<(ostream&out, Person&per);
friend istream& operator >> (istream&in, Person&per);
private:
int pid;
string name;
int age;
public:
~Person() {}
void print()
{
cout << age << "," << name << endl;
}
};
ostream& operator<<(ostream&out, Person&per)
{
out << per.pid << "," << per.name << per.age << endl;
return out;
}
istream& operator >> (istream&in, Person&per)
{
in >> per.pid >> per.name >> per.age;
return in;
}
void main()
{
Person per;
cin >> per;
cout << per;
system("pause");
}
算术运算和关系操作符重载,重载要看重载函数是否写完,只有写完才有小,在重载函数内部的不算该操作符被重载
class Person
{
friend bool operator==(Person&p1, Person&p2);
friend bool operator!=(Person&p1, Person&p2);
private:
int pid;
string name;
int age;
public:
Person(int pid, string name, int age) :pid(pid), name(name), age(age)
{
cout << "构造函数被执行" << endl;
}
~Person() {}
void print()
{
cout << age << "," << name << endl;
}
};
bool operator==(Person&p1, Person&p2)
{
//括号里面的等号,还是没有被重载,还是原来的含义
return (p1.pid == p2.pid&&p1.name == p2.name&&p1.age == p2.age);
}
bool operator != (Person&p1, Person&p2)
{
return !(p1 == p2);
}
void main()
{
Person p1(1,"tom",12);
Person p2(2, "tim", 13);
cout << (p1 == p2);
system("pause");
}
4.赋值操作符,赋值操作符必须是成员函数,以便告诉编译器是否需要合成一个,赋值操作符返回this引用
class Person
{
private:
int pid;
string name;
int age;
public:
Person(int pid, string name, int age) :pid(pid), name(name), age(age)
{
cout << "构造函数被执行" << endl;
}
~Person() {}
void print()
{
cout << age << "," << name << endl;
}
Person& operator=(Person&p1)
{
this->pid = p1.pid;
this->name = p1.name;
this->age = p1.age;
return *this;
}
void print1()
{
cout << pid << name << age << endl;
}
};
void main()
{
Person p1(1,"tom",12);
Person p2(2, "tim", 13);
p1 = p2;
p1.print1();
system("pause");
}
10.2.继承概述
1.1继承关系,可以通过继承达到代码重用的目的,被继承类称为父类或者超类,继承类称为子类或者源生类,子类可以继承父类的成员变量和成员方法,继承的语法格式如下
class 子类名称:[继承方式]父类名称
{
[访问修饰符:]
[成员列表]
}
,继承方式有private,public,protected,protected:受保护的子类可以使用,私有的子类不可以使用,构造函数不能被继承
class Person
{
protected:
int pid;
string name;
int age;
public:
Person(int pid, string name, int age) :pid(pid), name(name), age(age)
{
cout << "构造函数被执行" << endl;
}
~Person() {}
void print()
{
cout << age << "," << name << endl;
}
};
class Manager :public Person
{
private:
string job;
public:
// Manager();
//调用了父类函数初试化 //初试化父类直接调用了父类的构造函数来初始化
Manager(int pid, string name, int age ,string job) :Person(pid, name, age),job(job)
{
cout << "M构造函数被执行" << endl;
}
~Manager() {}
//覆盖父类的一个构造函数
void print()
{ //先调用父类的一个print
Person::print();
cout << age << "," << name << job << endl;
}
};
void main()
{
Manager m(1, "tom", 30, "Manager");
//调用父类的print
//m.Person::print();
//调用子类的print2
m.print();
system("pause");
}
class Manager :private Person.,先说一下我们继承一般都是公有继承1公有继承,子类的公有和保护成员与父类相同2.子类是私有继承时,父类的公有和私有成员在子类中都以私有形式出现,此时从父类继承下来的函数不能直接调用,在子类定义中是可以使用class son{};3.当类是保护继承时,父类的公有和保护成员都以保护成员成员身份出现在子类中。三种类型继承在非成员函数中都不能直接访问父类私有成员,上面三种的区别就是继承时,继承的是什么爸爸,一个公有爸爸,一个是私有爸爸,一个是保护型爸爸,保护型也只能在子类里面用,也就是只有子类的成员函数可以使用。
1.3继承关系中构造函数访问顺序,先调用父类的构造函数,再调用子类的构造函数,释放对象是先释放子类的析构函数,在调用父类的析构函数
1.4覆盖子类可以重写父类的方法,1.方法重载,在一个类当中,名称相同,函数的参数个数或类型不同,2.方法覆盖,继承的两个类里面,子类可以重写父类里面的同名函数
10.3多重继承
一个类可以有多个爸爸,语法格式如下
class 子类型名称:[继承方法]父类1,[继承方法]父类2
{
};
正常情况下一个类中公有的部分只可以该类使用,但是如果使用的是继承公有爸爸的时候,那么这个儿子也是可以使用父类的方法,
class Brid
{
public:
void fly()
{
cout << "flu..." << endl;
}
};
class Fish
{
public:
void swim()
{
cout << "can swim..." << endl;
}
};
class FlyFish :public Brid,public Fish
{
};
void main()
{
FlyFish ff;
ff.fly();
ff.swim();
system("pause");
}
十一.抽象类
11.1纯虚函数
1)纯虚函数只声明,而没有具体实现,这样的函数称为纯虚函数,语法格式virtual 返回值类型 函数名称(参数列表)=0;比起我们的普通函数前面加了一个virtual,后面多了一个=0,当一个类中只声明了一些接口(函数),具体没有实现让子类去实现,我们就需要定义一些虚函数,当类里面含有虚函数时我们称为抽象类,抽象类里面至少含有一个纯虚函数,
class USB
{
public:
virtual void read() = 0;//纯虚函数
virtual void write() = 0;
};
class Computer :public USB
{
//子类来实现读写方法
void read()
{
cout << "子类Computer的read" << endl;
}
void write()
{
cout << "子类Computer的write" << endl;
}
};
class Mobile :public USB
{
void read()
{
cout << "子类Mobile的read" << endl;
}
void write()
{
cout << "子类Mobile的write" << endl;
}
};
void main()
{
system("pause");
}
抽象类不能被定义实例化,也就是说下面的这种用法错误
void main()
{
抽象类 对象;//抽象类不能实例化
system("pause");
}
class USB
{
public:
virtual void read() = 0;//纯虚函数
virtual void write() = 0;
};
class Computer :public USB
{
//子类来实现读写方法
void read()
{
cout << "子类Computer的read" << endl;
}
void write()
{
cout << "子类Computer的write" << endl;
}
};
class Mobile :public USB
{
void read()
{
cout << "子类Mobile的read" << endl;
}
void write()
{
cout << "子类Mobile的write" << endl;
}
};
void main()
{
//抽象类不能实例化
//但是可以定义指针
USB *usb;
Computer c;
usb = &c;//初始化USB类型的指针
usb->read();
usb->write();
Mobile m;
usb = &m;//初始化USB类型的指针
usb->read();
usb->write();
delete usb;
usb = NULL;
system("pause");
}
十二.多态
虚函数是实现多态的基础,多态是指一个函数有多种状态,根据实际对象在运行期间动态调用相应的方法,要使用父类的一个指针或者是引用才能使用多态性,总体来说就是把抽象类在main中声明时,声明为指针可以改变指针指向,在运行期间可以动态的调用函数
抽象类
{
虚构函数
}
子类1
{
虚构函数的实现
}
子类2
{
虚构函数的实现
}
正常类
{
}
void main
{
抽象类 *p;
通过已经实例化的给p赋值
}
class USB
{
public:
virtual void read() = 0;//纯虚函数
virtual void write() = 0;
};
class Computer :public USB
{
//子类来实现读写方法
void read()
{
cout << "子类Computer的read" << endl;
}
void write()
{
cout << "子类Computer的write" << endl;
}
};
class Mobile :public USB
{
void read()
{
cout << "子类Mobile的read" << endl;
}
void write()
{
cout << "子类Mobile的write" << endl;
}
};
class Person
{
//要使用USB接口
public:
void use(USB &usb)//调用父类的一个引用,可以动态的改换usb所指向对象
{
usb.read();
usb.write();
}
};
void main()
{
Person per;
//抽象类不能实例化
//但是可以定义指针
USB *usb;
Computer c;
usb = &c;//初始化USB类型的指针
per.use(*usb);
Mobile m;
usb = &m;//初始化USB类型的指针
per.use(*usb);
system("pause");
}
多态的关键在下面,如果不是抽象类,那传入的usb引用只能是USB的对象,但是这里是抽象类之后,子类也可以传入作为usb,这里调用的关键在于指针和引用。
class Person
{
//要使用USB接口
public:
void use(USB &usb)//调用父类的一个引用,可以动态的改换usb所指向对象
{
usb.read();
usb.write();
}
};
十三,模板
模板的提出是为解决类型不同需要多次定义函数的麻烦问题,比如一个最大函数需要定义int,float类型等
int max(int a, int b)
{
return 0;
}
float max(float i, float j)
{
return 0;
}
13.1模板函数
模板定义格式是
template <typename T>
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
template <typename T>
inline T const& max1(T const& t1,T const &t2)//const是关键
{
cout << "模板函数被调用" << endl;
return t1>t2 ? t1 : t2;
}
//(T const& t1,T const &t2)
void main()
{
//求最大值
int n = max1(1, 2);
cout << n << endl;
float m = max1(3.14, 3.15);
cout << m << endl;
system("pause");
}
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
template <typename T>
T& sun(T &t1, T&t2)
{
return t1 + t2;
}
void main()
{
//int s = sum(1, 2);//这里会报错,因为我们引用传递,引用是变量,都没有变量肯定出错
//cout << s << endl;
system("pause");
}
修改方法有两种一是定义变量再引用
template <typename T>
T& sum(T &t1, T&t2)
{
return t1 + t2;
}
void main()
{
int a = 1;
int b = 2;
int s = sum(a,b);
//cout << s << endl;
system("pause");
}
方法二修改函数
template <typename T>
T sum(T t1, T t2)
{
return t1 + t2;
}
void main()
{
int s = sum(1,2);
//cout << s << endl;
system("pause");
}
模板是泛类编程的基础,模板分为函数模板和类模板,函数模板的定义如下
template<类型形式参数表>
返回类型 函数名(形式参数)//第二步和正常函数定义一样
{}
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
template <typename T>
T sum(T t1, T t2)
{
return t1 + t2;
}
void main()
{
int s = sum(1,2);
cout << s << endl;
float s1 = sum(1.2, 2.3);
cout << s1 << endl;
//类型不一致前面需要<>
int s2 = sum<int>(1, 2.3);
system("pause");
}
函数模板重载比如上面我们定义做数的加法,字符串的时候不太能满足要求我们就重写一个
template <typename T>
T sum(T t1, T t2)
{
return t1 + t2;
}
string sum(string s1, string s2)
{
return "hello";
}
void main()
{
int s = sum(1,2);
cout << s << endl;
float s1 = sum(1.2, 2.3);
cout << s1 << endl;
//传入的参数是string的时候先调用,重载之后又优先权
string s2 = sum("tom", "hong");
system("pause");
}
13.2类模板
类模板,使用template不仅可以定义函数模板,还可以定义类模板,类模板代表一族类,是用来描述通用数据类型或处理方法的机制,它使得类中的一些成员变量和成员函数的参数或返回值可以取任何类型的数据,类模板的声明语法格式,
template<类型形式参数表>class类模板名称
{
}
成员函数的声明
template<类型形式参数表>
返回类型 类模板名称<类型名称表>::成员函数名称(形式参数列表)
{
}
在这里,typename 是占位符类型名称,可以在类被实例化的时候进行指定。您可以使用一个逗号分隔的列表来定义多个泛型数据类型。
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
//template <class T1,class T2>,这里的class就代表任意类型,还可以这样定义
template<typename T1,typename T2>
class MyClass//类模板名称
{
private:
T1 t1;//比如T1可以是int,也可以是string
T2 t2;
public:
//使用构造函数来初试化
MyClass(T1 t1, T2 t2) :t1(t1), t2(t2)
{
}
void display()
{
cout << t1 << "," << t2 << endl;
}
};
void main()
{
//构造函数类型可以在实例化的时候被指定
//和普通的对象相比需要指明构造函数的类型
MyClass <int,double> myclass(100,3.14);
myclass.display();
//假设现在类型换了
MyClass <string,int> myclass("tom", 3);
myclass.display();
//run<int,double>
system("pause");
}