输入与输出
在C++中我们用输入流函数cin
和输出流函数cout
进行标准输入/输出操作,同时用到的运算符有流插入运算符<<
和流提取运算符>>
,使用时需要包含输入输出流的头文件<iostream>
,具体使用如下例:
#include<iostream>
#include<string>
using namespace std;
int main() {
string str;
cin>>str;
cout<<"Hello World!"<<endl;
return 0;
}
在C的标准输入里,我们可以指定数制进行输出,在C++中,我们也可以对整数指定数制输出,常用数制如下表:
数制 | 关键字 |
---|---|
八 | oct |
十 | dec |
十六 | hex |
注:不能想当然认为bin是二进制的关键字,C++中没有bin关键字
以上的数制使用setbase(int)
函数一样能实现,该函数的作用见下表:
参数值 | 作用 |
---|---|
8 | 以8进制输出 |
10 | 以10进制输出 |
16 | 以16进制输出 |
其他 | 将iosflags 重置为默认 |
所谓iosflags
,即输入输出流标记,该标记存在于ios_base::basefield
中,其有效值只有8,10,16,对应oct
,dec
,hex
关键字,因此bin是无效关键字,其中10为默认的iosflags
,重置即将从该位置往后的输出均使用10进制输出
注:
cout
中指定数制以后,直到输出结束或遇到下一次重新指定才改变,否则将一直以指定数制进行输出
使用setbase(int)
需要导入头文件<iomanip>
#include<iostream>
#include<iomanip>
using namespace std;
int main() {
int a = 10;
cout<<oct<<a<<" "<<dec<<a<<" "<<hex<<a<<" "<<setbase(32)<<a<<" "<<setbase(16)<<a<<" "<<endl;
return 0;
}
说到<iomanip>
,我们再来介绍另一个包含于其中的格式控制函数:setw(int)
setw(int)
的作用很简单,就是设置输出变量的宽度,不足则自动补齐,超过宽度则原样输出,补齐的方式和设置的对齐方式有关,C++默认左对齐,可以使用std::left
,std::right
设置对齐方式,样例如下:
#include<iostream>
#include<iomanip>
using namespace std;
int main() {
cout<<setw(4)<<1<<setw(4)<<1<<endl;
cout<<std::left<<setw(4)<<1<<setw(4)<<1<<endl;
cout<<std::right<<setw(4)<<1<<setw(4)<<1<<endl;
return 0;
}
注:
cout中指定宽度以后,直到输出结束或遇到下一次重新指定才改变,否则将一直以指定宽度进行输出
<iostream>
还含有其它输入/输出方式,cin
和cout
也带有其他方法,具体内容稍后介绍.
常变量
C使用#define
预编译命令定义常量,C++使用const
定义常变量,他们的区别如下:
方式 | 存储单元 | 数据类型 | 值 | 指针指向 |
---|---|---|---|---|
#define | 无 | 无 | 简单替换,不可改变 | 不能被指向 |
const | 有 | 有 | 一次赋值,不可改变 | 可以被指向 |
可见C++中const定义的常变量,既有常量的特点,又有变量的特点,简单例子如下:
#include<iostream>
#include<cmath>
using namespace std;
int main() {
double r = 10;
const double PI = 3.14; //和#define一样,常变量声明时必须有初值;
cout<<PI*pow(r,2)<<endl;
return 0;
}
函数重载与模板函数
对于功能相同,参数类型不同/参数个数不同的函数,我们可以使用重载(overload),即使用同名函数完成不同的功能,重载函数的条件为:
两个函数的参数类型、参数个数二者至少有一个不同,下面是一个简单例子:
#include<iostream>
using namespace std;
int Max(int a,int b) {
return a > b ? a : b;
}
double Max(double a,double b) {
return a > b ? a : b;
}
int Max(int a,int b,int c) {
if(a < b) a = b;
if(a < c) a = c;
return a;
}
int main() {
int a = 3,b = 5,c = 17;
double d = 8.8,e = 10.5;
cout<<Max(a,b)<<" "<<Max(a,b,c)<<" "<<Max(d,e)<<endl;
return 0;
}
从上面的例子我们可以看出double Max(double,double)
和int Max(int,int)
,仅仅是参数类型不一样,其余完全一样,为了提高代码的可重用性,我们可以将其写成函数模板(的形式,函数模板声明如下:
template<typename T>
T Max(T,T);
template<class T>
T Max(T,T,T);
即,将多个仅有参数类型不同的,实现完全一致的函数的参数类型用一个抽象类型typename T
来表示,实现的时候用T来作为类型实现函数体,实际调用的时候,采用简单替换的方式,将实际类型代入T的位置,即可完成对不同类型函数的实现,这样的函数叫做函数模板,比如:
#include<iostream>
using namespace std;
template<typename T>
T Max(T a,T b) {
return a > b ? a : b;
}
int main() {
int a = 3,b = 5;
double c = 19.1,d = 3.3;
cout<<Max(a,b)<<" "<<Max(c,d)<<endl;
return 0;
}
注:
返回值类型不同不可作为函数重载的条件
使用函数模板必须声明为模板(使用template
关键字),class
、typename
均可声明抽象类型
带有默认参数的函数
在C++中,允许函数参数带有默认值,当该位置不传实参进来时,函数使用默认参数,如:
int func(int a,int b = 0) {
return a*b;
}
func(5); //相当于func(5,0);
func(5,5);
注:
若有多个参数含有默认值,将这些参数声明于函数的最右方,如:
double fun(int a = 0,int b,int c,double d = 5.5) //语法错误;
double fun(int b,int c,int a = 0,double d = 5.5) //正确;
带默认值的参数不能进行重载,否则会引起程序的二义性
变量的引用
C++中引入引用类型,它和指针的区别大概是:引用的变量和原变量是同一变量,指针指向变量的存储地址,但指针不能代替原变量,虽然它可以通过修改指针指向的值来修改原变量内容,但若原变量是一个指针型变量,则用指针指向原变量将无法修改原变量的指向(只能修改当前指针的指向)
引用的最大用途就是作为参数传入函数中,达到修改实参值/修改实参指向的目的,以二叉树的建立为例:
struct BTT {
char data;
struct BTT *lc,*rc;
};
void initBTT(BTT *&T) {
char ch;
cin>>ch;
if(ch == '@') {
T = nullptr;
return;
}
T = new BTT;
T->data = ch;
initBTT(T->lc);
initBTT(T->rc);
}
上述代码中,若声明写成:
void initBTT(BTT *T)
则新建的二叉树在建立完成后,主函数传入的树根T将会无法指向这棵二叉树,导致对其操作时发生错误
而交换数据的函数用引用实现再好不过了:
void Swap(int &a,int &b) {
a ^= b ^= a;
}
忘了说引用的基本定义了:
int &a = b; //声明a是b的引用,即a是b的别名,a和b实际上是同一变量;
内联函数
对于码量短小且执行频繁的简单函数,C++支持使用内联函数,相当于预编译命令,它可以给编译器一个建议,即在编译之前将内联函数函数体嵌入到其调用的位置上去,可以减少编译时间,进而加快程序执行速度,内联的关键字是inline
注:inline
只是一个建议,编译器并不总是使用内联处理,为了让inline
起作用,建议将不含循环控制的(for、switch、while等)、代码长度不超过10行且使用频率较高的函数声明为inline
,举例如下:
template<typename T>
inline T Max(T a,T b,T c) {
if(a < b) a = b;
if(a < c) a = c;
return a;
}
作用域运算符::
C语言中,我们可以通过成员运算符.
或是->
(指针)访问结构体的成员变量,这些运算符在C++中依旧有效,除此之外,C++中还引入了作用域运算符::
,该运算符在C++的面向对象中使用较多,运算符的意义是作用域的某成员(field::member),注意,域运算符::
不能用来指定某个具体的结构的成员,因为作用域是抽象的,不能和具体化的对象混为一谈,下面写一些正确/错误的例子:
#include<iostream>
using namespace std;
int a = 5; //全局变量a;
struct T {
static string str;
int num;
};
int main() {
T t;
int a = 10;
cout<<a<<endl;
cout<<::a<<endl; //使用全局变量a;
t::str; //错误,t是T类型的具体对象;
T::str; //正确,T是抽象的类型;
return 0;
}
string类型
C++中字符串类型不再是使用char
型数组,而是有专门的string
类型,使用时导入头文件<string>
string
类型的常用方法和迭代器如下表:
函数 | 功能 |
---|---|
empty() | 判空 |
size() | 获取string长度 |
length() | 同上 |
max_size() | 返回当前环境下string能分配的最大空间 |
resize(int n,char ch) | 给string重新分配长度,多补字符ch少截取原则 |
resize(int n) | 给string重新分配长度,多补字符’ '少截取原则 |
clear() | 清空string |
front() | 指向第一个字符 |
back() | 指向最后一个字符 |
pop_back() | 删除最后一个字符 |
push_back(char ch) | 在最后插入一个字符ch |
substr(int st,int end) | 截取从st到end-1长度的子串 |
assign() | 整体编排string,有多种重载形式,不能一一举例 |
对于string
类型,需知道它是一个类型,其数据成员是私有的,因此虽然可以通过[]
运算符访问其单个字符,但无法改变其值可以使用迭代器(常量迭代器除外)对其单个字符进行修改,以下列出了所有的迭代器:
迭代器 | 说明 |
---|---|
begin() | 起始迭代器 |
end() | 结束迭代器 |
rbegin() | 逆置起始迭代器 |
rend() | 逆置结束迭代器 |
cbegin() | 常量起始迭代器 |
cend() | 常量结束迭代器 |
rcbegin() | 逆置常量起始迭代器 |
rcend() | 逆置常量结束迭代器 |
同样可以使用部分算数运算符和逻辑运算符对string
进行操作:
运算符 | 功能 |
---|---|
+ | 起连接作用 |
+= | 同上 |
= | 赋值 |
== | 判等 |
!= | 判不等 |
< | 按strcmp()的规则比较,返回bool值 |
<= | 按strcmp()的规则比较,返回bool值 |
> | 按strcmp()的规则比较,返回bool值 |
>= | 按strcmp()的规则比较,返回bool值 |
关于string
的方法,无法全部列出,有兴趣可以查阅工具书,下面写一个简单的标程:
#include<iostream>
#include<string>
using namespace std;
int main() {
string str1 = "I love C",str2;
cout<<str1<<endl;
str1.resize(str1.size()+2,'+'); //将str1的长度+2,用'+'填充额外长度;
cout<<str1<<endl;
cout<<str1.substr(0,8)<<endl; //截取str1从0~7的子串;
for(string::iterator it = str1.begin();it < str1.end()-2;it++) //迭代器修改单个元素的值;
*it = '0';
cout<<str1<<endl;
for(string::reverse_iterator it = str1.rbegin();it < str1.rend()-2;it++) //反向迭代器修改单个元素的值;
*it = '9';
cout<<str1<<endl;
str1.assign(str1.rbegin(),str1.rend()); //逆置str1;
cout<<str1<<" "<<str1.size()<<endl;
str2.assign(str1.begin(),str1.end()-2); //将str1的0~size-2个字符给str2;
cout<<str2<<" "<<str2.length()<<endl;
cout<<str1.back()<<endl; //输出str1的最后一个字符;
cout<<str2.front()<<endl; //输出str2的第一个字符;
for(int i = 0; i < 3; i++) {
str1.push_back(cin.get()); //输入三个字符,插入到str1最后;
str2.pop_back(); //删除str2最后三个字符;
}
cout<<str1<<" "<<str2<<endl;
if(str1 > str2) cout<<"Yes!"<<endl; //比较;
str2 = str1; //赋值;
while(!str1.empty()) { //清空str1;
str1.pop_back();
}
str2.clear(); //清空str2;
if(str1.empty() && str2.empty())
cout<<"Cleared!"<<endl;
return 0;
}
new和delete运算符
关于new
和delete
,只需知道以下几点:
1.new
关键字对应C中的malloc(sizeof type)
,delete
相当于free(pointer)
,作用分别是为某个类型分配空间,撤销空间,和malloc()
不同的是,new
返回该类型的指针,malloc()
返回没有类型的指针
2.使用new
动态建立的空间,必须使用delete
删除,它们总是成对出现(类比malloc()
和free()
)
3.关于new
和delete
的配合使用,大概是以下这样:
int *a = new int; //申请单个空间;
delete a; //单个释放;
a = nullptr;
int *b = new int[10]; //申请连续空间;
delete[] b; //连续释放;
b = nullptr;
4.无论是delete
或free()
,释放空间均没有将指针置空,请自动置空(nullptr
)