一、输入与输出
C++语言并没有定义任何输入输出语句,而提供一个全面的标准库来提供IO机制。
iostream包含两个基础类型:istream和ostream分别表示输入和输出。
cin(istream)为标准输入;cout(ostram)为标准输出;cerr和clog(ostream),cerr用来输出警告和错误信息,clog用来输出程序运行时的一般性信息。
std::cout<<"ostream:"<<std::endl;//输出
int i=0;
std::cout<<"请输入一个数字:";
std::cin>>i;//输入
cin(>>)和cout(<<)是程序中最常用的。
上面代码段中的endl相当一个换行符 '\n';
上面程序中使用了std::cout而不是cout,前缀std::是指出cout和cin、endl是定义在名为std的命名空间中的,标准库定义的所有名字都在命名空间std中。
全部像这样写太过于烦锁:我们可以使用using namespace std;来声明。
#include "iostream"
using namespace std;
int main(){
cout<<"ostream:"<<endl;//输出
int i=0;
cout<<"请输入一个数字:";
cin>>i;//输入
}
就可以直接写cout和cin、endl了。
C++算术类型:
它分为两类:整形和浮点型
类型 | 含义 | 最小尺寸 |
bool | 布尔类型 | 未定义 |
char | 字符 | 8位 |
wchar_t | 宽字符 | 16位 |
char16_t | Unicode字符 | 16位 |
char32_t | Unicode字符 | 32位 |
short | 短整形 | 16位 |
int | 整形 | 16位 |
long | 长整形 | 32位 |
long long | 长整形 | 64位 |
float | 单精度浮点数 | 6位有效数字 |
double | 双精度浮点数 | 10位有效数字 |
long double | 扩展精度浮点数 | 10位有效数字 |
上表中的Unicode是用于表示所有自然语言中字符的标准。
布尔类型(bool)的取值只能是真(true)或假(false)。
当我们写程序的时候定义的变量,建议对每一个内置的类型都进行初始化。
带符号类型(signed)和无符号类型(unsigned)
除了布尔型和扩展的字符型之外,其他整形都可以划分为带符号的和不带符号的。带符号的可以表示负数,而不带符号只能表示大于或是等于0的值。
类型 int 、short 、long 、long long 都是带符号的,在它们前面加unsigned就可以成为无符号型,主要记住它们本身是有符号的。其他中无符号整形可以缩写为:unsigned 。
变量声明和定义的关系:
extern int i;//这是声明i而非定义i
int j;//这是声明并定义j
变量只能被定义一次在同一个作用域下,但是被声明多次。
建议在定义变量的时候,第一次使用的时候现定义它,不要提前定义它。
C++的标识符由字母、数字、下画线组成,必须以字母或下画线开头。标识符长度没有限制,但是对大小写敏感,命名时不能和C++的关键字一样。
这里也说一下变量名的命名规范:
1.标识符尽量要体现实际含义。
2.变量名一般用小写字母。
3.自己定义的类名一般以大写字母开头。
4.标识符由多个单词组成,则单词间应该有明显区分的标志。
名字的作用域:
作用域:C++语言中的大多数作用域都以花括号分隔,名字的有效区域始于名字的声明语句,以声明语句所在的作用域末端为结束。
#include <iostream>
int main(){
int sum=0;
for(int i=0;i <=10 ; i++){
sum+=i;//等价于sum = sum +i
}
cout<<"sum= "<<sum<<endl;
return 0;
}
全局作用域(全局变量):就是在代码执行的全过程中都可以访问;局部作用域(局部变量):就像上面代码中的 i 变量,它定义在for循环里,一但出了循环体,就访问不到它了,但是可以又可以在循环体外面,重新定义它。
嵌套作用域:包含着别的作用域的作用域叫外层作用域;被包含的作用域叫做内层作用域。
建议一下:如果函数有可能用到某个全局变量,就不要再定义一个同名的局部变量。
引用(别名):
引用就是对象起另外一个名字。
int ival =1024;
int &refval = ival;//这个refval就是变量ival的另一个名字,它们两个操作的是同一个空间
当我们作用引用的时候必须初始化它,必须初始化,否则会报错。引用不是一个对象,它只是已经存在的对象的另一个名字。
指针(基础):
指针是指向另外一种类型的复合类型。指针本身是一个对象,指针无须在定义时赋初值。
int *p1,*p2; //p1和p2都是指向int型对象的指针
double dp , *dp1; //dp是一个正常的double型的对象,*dp1是一个指向double型对象的指针
int ival = 42;
int *p = &ival; //p存放变量ival的地址,或者说p是指向变量ival的指针
//指针指向的类型必须和所指向内容的类型相同。下面的是错误的:
int ival1 =43;
char * p1 =&ival1;//它们的类型不匹配
空指针:就是指针没有指向任何对象。
int *p1 = nullptr;//C++11的新标准引入的一种方法
int *p2 = 0;
int *p3 = NULL;
//以上三种都是空指针,它们不指向任何对象
C++程序员最好使用nullptr去初始化空指针,同时避免使用NULL;
利用指针访问对象:如果指针指向了一个对象,则我们可以使用解引用符(*)来访问指针所指向的对象。
int ival =42;
int *p =&ival;
cout<< *P;//能过指针P的解引用就可以输出ival的值42;
*P =0;//可通过指针改变指针所指对象的值
cout<< *p;//输出0
建议初始化所有指针,使用未经初始化的指针是引发运行时错误的一大原因。
当指针做条件时:
任何非0指针对应的条件都是true。
int ival = 1024;
int *pi = 0 ;//是一个空指针,它的值为0
int *pi2 = &ival;
if(pi)//条件为false,因为pi的值是0
//...
if(pi2) //pi2指向对象的值不为0,因些条件为true
//...
void* 是一种特殊的指针类型,它可以存放任意对象的地址。我们不能直接操作它所指向的对象,因为不知道它所指向对象的类型是什么,并且它能做的事比较有限。以void*的视角来看内存空间也就仅仅是内存空间,没办法访问内存空间中所存的对象。(这个作为了解就行了)
double obj =3.14 , *pd = &obj;
void * pv =&obj;//void* 能存放任意类型对象的地址;obj可以是任意类型的对象
pv= pd; //pv可以存放任意类型的指针
复合类型的声明:
int i =1024 , *p =&i , &r =i ;
//i是一个整形的数,p是一个int型的指针,r是一个 int 型引用
int* p; // p是int型的指针,而不是int*型的
int* p1 , p2;
//p1是指向int 型的指针, p2 是int型的变量
指向指针的指针:**表示指向指针的指针,***表示指向指针的指针的指针。 以此类推
int ival =1024;
int *p =&ival;//p指向一个Int型的数
int **pp=π//pp指向一个Int型的指针
指向指针的引用:
引用本身不是一个对象,所以不能定义指向引用的指针。但是指针是对象,所以存在对指针的引用
int i =42;
int *p; //p是一个int型的指针
int *&r = p ;//r是一个对指针p的引用
r= &i;//r引用了一个指针,给r赋值&i就是令p指向i
*r =0;//解引用r得到i,也就是p所指向的对象,将i的值改为0
我们理解r的类型是什么,最简单的方法是从右向左阅读r的定义。当我们遇到比较复杂的时候
const限定符:
当我们想要定义一种变量的值不能被改变,就可以用const限定符加于限制(整形常量)
const int du = 520;
du = 520; //这是错误的,du的值是不能被改变的。
const对象一旦创建它的值就不能被更改,所以const对象必须初始化。
int i= 42;
cin>>i;
const int ci =i; //i的值被拷贝给了ci
int j = ci; //ci 的值被拷贝给了j
可以使用定义好的对象去初始化一个const对象。当我们输入i的值后,拷贝给了ci,那么ci的值就不可以改变了。
在默认情况下,const对象仅在文件内有效,就是只在声明并定义它的那个文件内有效,其他文件无法使用它,我们可以利用extren来实现:
extern const int i=1100;
要在多个文件之间共享const对象,必须在变量的定义之前添加extren关键字。
const的引用(常量引用):
可以把引用绑定到const对象上(对常量的引用)。与普通引用不同,对常量的引用不能被修改它所绑定的对象:
const int ci = 1024;
const int &ri =ci;//正确的:引用的对象是常量
ri = 42; // 错误:ri是对常量的引用
int &r2= ci; //不能让一个非常量引用指向一个常量对象
引用的类型必须和所引用对象的类型一致。但是有两个例外:
就是在初始化常量引用时允许用任意表达式作为初始值,只要这个表达式的结果能转换成引用的类型就可以。
double dval = 3.14;
const int &ri1 =dval;
//上面的代码编译器会改变成下面这样:
const int temp = dval;
const int &ri1 =temp;//这样ri1绑定了一个临时量
如果上面的代码中ri1不是一个常量时,就是把引用绑定到临时量上,这种行为在C++中会归为非法,下面的代码是错误的,会报错,不允许像这样写!!!
double dval =3.14;
int &ri=dval;
对const的引用可能引用一个并非const的对象:
//对const的引用可能是一个非const的对象
int i = 42;
int &fi= i; //把引用fi绑定对象i
const int &rh=i;// rh也绑定对象i,但是不允许通过rh去改变i的值
i=0;//i并非一个常量,所以可以更改它的值
rh=0; //错误,rh是一个常量引用,不能直接改变它的值
cout<<"i= "<<i<<endl;
cout<<"rh= "<<rh<<endl;
指针和const:
要想存放常量对象的地址,只能使用指向常量的指针;
const double pi = 3.14;
double *ptr = π//错误,因为Ptr是一个普通的指针
const double *cptr = π //正确的,它们的类型相同
*cptr = 42;//错误,不能给*cptr赋值
允许令一个指向常量的指针指向一个非常量对象。和常量引用一样,指向常量的指针也没有规定其所指的对象必须是一个常量。指向常量的指针仅仅是要求不能通过这个指针去改变对象的值,而没有规定那个对象的值不能通过其他途径改变。
const 指针(常量指针):
指针是一个对象,所以允许将一个指针本身定义为常量。常量指针也必须初始化。一旦初始化后,它的值(它存放的地址)就不能再更改了。
把*放在const关键字之前用以说明指针是一个常量(不变是的指针本身的值而不是指向的那个值)。
int nub= 0;
int *const cur = & nub;//cur将会一直指向nub,不会改变
cosnt double pi = 3.124;
const double *const pip =π//pip是一个指向常量对象的常量指针
遇到复杂的定义用我们前面讲过的方法:从右向左阅读。
上面代码中的cur为例:cur旁边是const(从右向左)说明cur本身是一个常量,然后下一个符号是*,表示是一个指针,最后综合得到cur是一个常量指针。(如果看到这里,请利用这种方法说出pip的类型打在评论区)
顶层const:
顶层const:表示指针本身是个常量。
底层const:表示指针所指的对象是一个常量。
指针可以是顶层const的同时也是底层const。
int i = 0;
int *const p1 =&i; //不能改变P1的值,这是一个顶层const
const int ci = 42; //不能改变ci的值,这是一个顶层const
const int *p2 =&ci;//允许改变p2的值,这是一个底层const
const int *const p3 = p2; //靠右的const是顶层const ,靠左的是底层const
const int &r =ci ; //用于声明引用的const都是底层const
当执行对象的拷贝操作时,常量是顶层const还是底层const区别明显,但是顶层const不受影响:
i =ci ;//正确的:拷贝ci的值,ci是一个顶层const,这个作用无影响
p2 = p3;//正确的:p2和p3指向的对象类型相同,p3顶层const的部分不影响。
//执行拷贝操作并不会改变拷贝对象的值,拷入和拷出的对象是否是常量都没有什么影响。
而底层const的限制不能忽视:
当执行拷贝操作时,拷贝的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。一般来说:非常量可以转换成常量,反过来则不行:
int *P = p3;// 错误:p3有底层const的定义,但是P没有
p2 = p3;//正确:p2 和p3都是底层const
p2 = &i; //正确:int*能够转换成const int*
int &r =ci;//错误:普通的int&不能绑定到Int常量上
const int &r2= i;//正确:const int&可以绑定到一个普通Int上
上面的代码中,p3是顶层const同时也是底层const,拷贝p3的时候可以不在乎它是一个顶层const,但是必须清楚它指向的对象是一个常量(要注意它的底层const)。p3的值可以赋给p2,是因为它们两个都具有底层const的性质,虽然p3同时也具有顶层const的性质,就这次赋值不会有什么影响。