—编译器为codeblocks 13.12中的MinGW—
空指针:
空指针不指向任何对象,在试图使用一个指针之前代码可以首先检查它是否为空。
生成空指针的方法:
int *p1= nullptr; //c++11标准,等价于int *p1 = 0;
int *p2= 0; //直接将p2初始化为字面常量0
int *p3= NULL; //在stdlib库中,等价int *p3 =0;
//其中nullptr是一种特殊类型字面值,它可以被转换成任意其他指针类型。
类型相同的合法指针可以用操作符(==)和(!=)来比较,比较结果是布尔型。
当两个指针存放的地址相同时返回true,两个指针存放的地址值相同有三种可能:
1.两个指针都为空 2. 都指向同一个对象 3.都指向同一个对象的下一地址
void* 指针
可以存放任意对象的地址,但是我们队该地址中到底是个什么类型的对象并不了解。
void* 可以用来:和别的指针比较,作为函数的输入或输出,赋给另外一个void*指针。
void* 不能直接操作void* 所指的对象,因为我们不知道这个对象是什么类型。
double obj =3.14, obj2 =3.15, *pd = &obj ;
void *pv=&obj;
if(pv==pd)//比较结果为1
cout<<1<<endl;
else
cout<<0<<endl;
cout<<*pv<<endl;//错误
指向指针的引用:
引用本身不是一个对象,因此不能定义指向引用的指针。但指针是对象,所以存在对指针的引用:
int i = 42;
int *p;
int *&r= p;//r是一个队指针p的引用
r = &i;//r引用了一个指针,给r复制&i就是令p指向i。
cout<<*r<<" "<<*p<<endl;//42 42
*r =0;//解引用r得到i,也就是p指向的对象,将i的值变为0
cout<<*r<<" "<<*p<<endl;//0 0
要理解r的类型到底是什么,从右向左阅读r的定义。离变量名最近的符号&对变量类型有最直接影响。因此r是一个引用,声明符的其余部分用以确定r引用的类型是什么,此例中的符号*说明r引用的是一个指针。最后剧本数据类型是int。
const的引用:
把引用绑定到const对象上,叫做对常量的引用。对常量的引用不能用作修改它所绑定的对象。
const int ci=1024;
const int &r2=ci;
r2=42; //错误,r2是对常量的引用
int &r2=ci; //错误,非常量引用指向一个常量对象
初始化问题:
初始化常量引用允许用任意表达式作为初始值,只要该表达式的结果能转换成引用类型即可。尤其,允许为一个常量引用绑定非常量对象,字面值,甚至是一个表达式。
int i=42;
const int &r1=i;
const int &r3=r1*2;
cout<<r1<<endl;//结果 42
cout<<r3<<endl;//结果 84
i++;
cout<<r1<<endl;//结果43
r1++;//错误,r1只读
//r1的例子表明,常量引用绑定到一个非常量对象上后,不能够通过该引用改变该对象的值,但是如果该对象的值改变,常量引用的值也会跟着改变。
cout<<r3<<endl;//结果84
//可以这样理解,r3绑定的是一个值,而不是一个变量。所以当i或r1改变的时候,r3的值始终为42*2=84
int i=42;
int j=50;
const int &r3=i*2;
cout<<r3<<endl;//结果 84
i=j;//接上面的例子,注意此时的r3绑定到i*2。但即使是i改变,r3的值也没有改变。
cout<<r3<<endl;//结果84
当一个常量引用被绑定到另外一种类型上时发生了什么?
double dval = 3.14;
const int &ri=dval;
cout<<dval<<endl;//结果3.14
cout<<ri<<endl;//结果3
dval*=2;
cout<<dval<<endl;//结果6.28
cout<<ri<<endl;//结果依然是3
引用书上的话,为了确保让ri绑定一个整数,编译器把上诉代码编程了如下形式:
const int temp= dval;
const int &ri=temp;
ri绑定了一个临时量对象,所谓淋湿了对象就是当编译器需要一个空间暂存表达式求值结果时临时创建的一个未命名的对象。 我个人感觉,也可以像之前r3的例子一样,此时ri绑定的是一个转化后的值,即由double变成int的值,而不是对象本身。所以当dval变化的时,ri永远都是3。
//为了验证上面的话,做以下实验
int i=42;
int &r1=i;
const int &r2=i;//此时r2绑定到i上,i是一个常整形变量
cout<<i<<endl;//42
cout<<r1<<endl;//42
cout<<r2<<endl;//42
i=50;
cout<<i<<endl;//50
cout<<r1<<endl;//50
cout<<r2<<endl;//50
r1=100;
cout<<i<<endl;//100
cout<<r1<<endl;//100
cout<<r2<<endl;//100
int i=42;
int &r1=i;
const int &r2=i*2;//r2绑定到一个i*2上面,按我的想法,就是r2绑定到一个数上面
cout<<i<<endl;//42
cout<<r1<<endl;//42
cout<<r2<<endl;//84
i=50;
cout<<i<<endl;//50
cout<<r1<<endl;//50
cout<<r2<<endl;//84
r1=100;
cout<<i<<endl;//100
cout<<r1<<endl;//100
cout<<r2<<endl;//84
int i=42;
int &r1=i;
const int &r2=r1*2;//r2绑定到一个ri*2上面,按我的想法,就是r2绑定到一个数上面
cout<<i<<endl;//42
cout<<r1<<endl;//42
cout<<r2<<endl;//84
i=50;
cout<<i<<endl;//50
cout<<r1<<endl;//50
cout<<r2<<endl;//84
r1=100;
cout<<i<<endl;//100
cout<<r1<<endl;//100
cout<<r2<<endl;//84
上面的3个例子没显示出我的想法是错误的,我就这么记了=_=
指针和const:
指向常量的指针不能用于改变所指对象的值。想要存放常量对象的地址,只能使用指向常量的指针。
允许令一个指向常量的指针指向一个非常量对象,和常量引用一样,指向常量的指针也没规定其所指的对象必须是一个常量,所谓指向常量的指针很仅仅要求不能通过该指针改变对象的值,而且没有规定那个对象的值不能通过其他途径改变。
const double pi=6.28;
double *ptr=π//错误ptr是一个普通指针
const double *cptr=π
*cptr=42;//错误,不能给*cptr赋值
int main()
{
ios::sync_with_stdio(false);
double dval=3.14;
cout<<*cptr<<endl;//6.28
cptr=&dval;
cout<<*cptr<<endl;//3.14
dval*=3;
cout<<*cptr<<endl;//9.42
return 0;
}
const指针
常量指针注意和之前的指向常量的指针区别开,常量指针必须初始化。而且一旦初始化完成,则它的值就不能改变了。把*放在const关键字之前用以说明指针是一个常量,即不辨的是指针本身的值而非指向的那个值:
int errNumb = 0;
int tmp=1;
int *const curErr = &errNumb;
curErr=&tmp;//错误 常量指针是一旦初始化以后就不能在指向别的量,因为const距离curErr最近,说明它是一个常量,即这个值不是不能变的,随后告知这个常量的类型是一个int*类型。
cout<<*curErr<<endl;//0
cout<<errNumb<<endl;//0
*curErr=100;
cout<<*curErr<<endl;//100
cout<<errNumb<<endl;//100
指向常量对象的常量指针
const double pi=3.14;
const double tmp=1.1;
const double *const pip= π
pip=&tmp;//错误,pip是一个常量指针,不能更改指向
cout<<*pip<<endl;//3.14
顶层const 和底层const:
指针本身是不是常量以及指针所指的是不是一个常量就是两相互独立的问题,按照我的理解,比如有个指针p,p所指向的对象是不是固定不变的以及p所指向对象的值是不是固定不变的是两个独立的问题。
这里,用顶层const表示指针本身是个常量。
int b = 10;
int a= 100;
const int c= 1;//顶层const
int *const ptr=&b;//常量指针
cout<<b<<endl;//10
cout<<*ptr<<endl;//10
(*ptr)++;//ptr指向对象的值可以变
cout<<b<<endl;//11
cout<<*ptr<<endl;//11
ptr=&a;//错误,不能改变ptr的值,也就是ptr的指向。因为它是一个顶层const
底层const 表示指针所指的对象是一个常量。不允许通过指向常量的指针改变所指向对象的值,但是可以改变指向常量指针所指的对象。
const int ci =42;
const int ck = 50;
int cj=100;
const int *p2 =&ci;//p2的指向可以改变,但是不能通过p2改变所指对象的值。
cout<<*p2<<endl;//42
(*p2)++;//错误
p2=&ck;
cout<<*p2<<endl;//50
p2=&cj;
(*p2)++;//错误
cout<<*p2<<endl;//100
cj++;
cout<<*p2<<endl;//101
一个指针既可以是顶层const也可以是底层const,按照我的理解就是const可以限制在指针所指向的对象上,也可以限制在指针所指向对象的值上面。
int p2 = 100;
int p3 = 200;
const int p4= 300;
const int *const cp = &p2;
cout<<*cp<<endl;
p2++;
cout<<*cp<<endl;
cp=&p3;//错误,不可改变cp的指向
cp=&p4;//错误,同上
int i=0;
const int *const p3 = &i;
const int * cp=&i;
int *const icp= &i;
cout<<*p3<<endl;//0
cout<<*cp<<endl;//0
cout<<*icp<<endl;//0
i++;
cout<<*p3<<endl;//1
cout<<*cp<<endl;//1
cout<<*icp<<endl;//1
//这里没涉及到拷贝与赋值的问题,对于一个指针,只要是指向了一个普通变量,普通变量的值改变。无论什么限制都不好使
关于顶层const和底层const的拷贝操作:
真让人头大= =
书中介绍说顶层const在拷贝到其他变量中时不受什么影响。
int i = 0;
const int ci = 42; // 不能改变 ci 的值,这是一个顶层 const
const int *p2 = &ci; // 允许改变 p2 的值,这是一个底层 const
const int *const p3 = p2; // 靠右的 const 是顶层 const ,靠左的是底层 const
i = ci; // 正确:ci 是一个顶层 const,对此操作无影响
p2 = p3; // 正确:p2 和 p3 指向的对象的类型相同,p3 顶层 const 的部分不影响
底层cons的限制不能忽略
要求拷出和拷入的对象有相同的底层 const 资格或者能转换为相同的数据类型,一般非常量能够向常量转换,反之则不行。
int i = 0;
const int ci = 42; // 不能改变 ci 的值,这是一个顶层 const
const int *p2 = &ci; // 允许改变 p2 的值,这是一个底层 const
const int *const p3 = p2; // 靠右的 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 上
指针和constexpr:
在constexpr声明中定义了一个指针,限定符constexpr仅对指针有效,与指针所指对象无关。
int a=10;
int b=20;
constexpr int *q = &a;
(*q)++;
cout<<*q<<endl;//11
q=&b;//错误,指针q是一个指向整数的常量指针
依据以上代码,可以感觉constexptr int q=&a和int const q= &a;类似
数组,指针和引用:
指针和数组的组合
//包含n个指针的数组
constexpr int sz = 4;
int *parr[sz];
int a[4];
for(int i = 0;i<4;i++)
parr[i] = &a[i];//四个指针分别指向数组a中的四个元素
for(int i = 0 ; i<4 ;i++)
*parr[i] = i;//通过指针赋值
for(int i = 0 ;i < 4;i++)
cout<<a[i]<<" ";//输出
cout<<endl;
//指向一个数组的指针
int (*parr)[4];
int a[4],b[10];
parr = &b;//错误,必须是[4]个元素
for(int i = 0;i < 4 ;i++)
a[i] = i;
parr = &a;
for(int i = 0;i < 4 ;i++)
cout<<(*parr)[i]<<" ";
cout<<endl;
数组和引用的组合
//不存在引用的数组,int &refs[10]=....
//引用一个含有4个整数的数组
int a[4];
for(int i = 0;i < 4 ;i++)
a[i] = i;
int (&parr)[4] = a;
for(int i = 0;i < 4; i++)
parr[i]++;
for(int i = 0;i < 4; i++)
cout<<a[i]<<" ";// 1 2 3 4
cout<<endl;
书上的一个例子
int a[4];
int *pa[4];
int *(&arry)[4] = pa; // 一个arry是数组的引用,该数组里含有4个指针
for(int i = 0; i < 4; i++)
{
a[i] = i;
pa[i] = &a[i];
}
for(int i = 0 ;i < 4; i++)
(*arry)[i]++;
for(int i = 0; i < 4 ;i++)
cout<<a[i]<<" ";
cout<<endl;
for(int i = 0 ;i < 4; i++)
cout<<(*pa)[i]<<" ";
cout<<endl;
C++11中的指针和数组新特性:
auto,decltype在指针和数组中的注意事项
int ia[] = {0,1,2,3,4,5,6};
auto ia2(ia);//这里的auto推断得到的类型是指针不是数组。
// ia2 = 4;//错误
ia2 = &ia[3];//3
cout<<*ia2<<endl;
decltype(ia) ia3 = {1,2,3};//但是decltype推断出的类型ia3是一个含有7个整数的数组
for(auto x : ia3)
cout<<x<<" ";//1 2 3 0 0 0 0
cout<<endl;
用标准库函数begin和end
int ia[] = {0,1,2,3,4,5,6};
int *beg = begin(ia);//相当于迭代器,begin指向数组首地址,end指向最后一个元素的下一个位置
int *last = end(ia);
for( ;beg != last ; beg++)
cout<<*beg<<" ";
cout<<endl;
使用范围for语句处理多维数组
int ia[5][5] = {0};
size_t cnt = 0;
for(auto &row : ia)//第一个for中要用引用类型,如果不是引用类型auto会把row转换成首元素的指针
for(auto &col : row ){//那么第二层循环就不合法
col = cnt;
cnt++;
}
for(auto col : ia[3])//类似这种情况
cout<<col<<" ";
cout<<endl;
要使用范围for语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。
使用标准库函数begin和end实现遍历二维数组
int ia[5][5] = {0};
size_t cnt = 0;
for(auto &row : ia)
for(auto &col : row ){
col = cnt;
cnt++;
}
for(auto p = begin(ia); p != end(ia) ;++p){//和用指针遍历没啥区别
for(auto q = begin(*p) ;q != end(*p) ;++q)
cout<<*q<<" ";
cout<<endl;
}
to be continue~