指针的引用
//第一种情况
#include<iostream>
usingnamespace std;
intmain()
{
int a = 1;
int b = 2;
int *tmp = &a;
int *p = tmp;
p = &b;
*p = 5;
cout<<"a::"<<a<<endl
<<"b::"<<b<<endl
<<"*tmp::"<<*tmp<<endl
<<"*p::"<<*p<<endl;
system("pause");
return 0;
}
//第二种情况
#include<iostream>
usingnamespace std;
intmain()
{
int a = 1;
int b = 2;
int *tmp = &a;
int *&p = tmp;//指向指针的引用
p = &b;
*p = 5;
cout<<"a::"<<a<<endl
<<"b::"<<b<<endl
<<"*tmp::"<<*tmp<<endl
<<"*p::"<<*p<<endl;
system("pause");
return 0;
}
p是一个指针,并且是指针tmp的引用,也就是说tmp和p之间建立了引用关系,相互“绑定”。在本例中tmp和p并没有脱离“绑定”关系,因此后面引用的使用是合法的。p=&b将tmp的指向变为了b,因此b,*tmp, *p都是5。Tmp和p指针之间的绑定关系没有改变,但是其指向是可以改变的,比如我们可以写程序测试tmp指针和p指针所指向的内存的地址。
测试:
#include <iostream>
using namespace std;
int main()
{
int a = 1;
int b = 2;
int *tmp = &a;
int *&p = tmp;//指向指针的引用
//add for test
cout<< "before:::::::::::::::::::"<< endl;
cout<< "tmp == " << tmp << endl;
cout<< "p == " << p << endl;
cout<< "&a == " <<&a << endl;
cout<< "&b == " <<&b << endl;
p = &b;
*p = 5;
//add for test
cout<< "after:::::::::::::::::::"<< endl;
cout<< "tmp == " << tmp << endl;
cout<< "p == " << p << endl;
cout<< "&a == " <<&a << endl;
cout<< "&b == " <<&b << endl;
cout<<"a::"<<a<<endl
<<"b::"<<b<<endl
<<"*tmp::"<<*tmp<<endl
<<"*p::"<<*p<<endl;
system("pause");
return 0;
}
另外:
在**项目中,有一个方法:
voidkillAnyTimer(QTimer*&timer)
{
if(timer)
{
if(timer->isActive())
{
timer->stop();
}
delete timer;
timer=NULL;
}
}
形参是指针的引用,如果形参类型是指针类型而不是指针的引用,则可能出现问题,因为这样的话timer形参是实参的一个副本,共同指向一个待delete的QTimer对象,因此如果在此函数体中将timer delete是可以的,但是实参指针仍然指向这个已经废弃掉的内存空间,如果不小心再次使用那个指针则可能出错。而且最后的timer=NULL,也只是对形参而言,实参其实不是NULL。
const引用(常引用)
引用本来就是绑定关系不能改变的(类似于const指针),也就是说引用一旦建立,则引用对象和被引用对象的“绑定关系”至死方休。
int a;
const int &ra=a;
用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,从而达到了引用的安全性。
#include "stdafx.h"
#include <iostream>
using namespace std;
int main()
{
int a;
const int&ra=a;// const references
int *p=&a;
//ra=3;//error,不能通过ra来修改a的值
a=5;//ok,但是可以用其他方法修改a的值,例如可以直接对变量a赋值
cout<<"a="<<a<<endl
<<"ra="<<ra<<endl;
*p=6;//ok,但是可以通过其他方法修改a的值,例如运用指向a变量的指针变量进行修改
cout<<"a="<<a<<endl
<<"ra="<<ra<<endl;
system("pause");
return 0;
}
其中着色部分发生编译错误:
1>------ 已启动生成: 项目:test_i, 配置: Debug Win32 ------
1>正在编译...
1>test_i.cpp
1>g:\vs2008project\test_i\test_i\test_i.cpp(10): error C3892: “ra”: 不能给常量赋值
1>生成日志保存在“file://g:\VS2008Project\test_i\test_i\Debug\BuildLog.htm”
1>test_i -1 个错误,个警告
========== 生成: 成功0 个,失败1 个,最新0 个,跳过0 个==========
将着色语句注释掉之后,编译和执行正确:
#include "stdafx.h"
#include <iostream>
using namespace std;
int main()
{
int a;
const int&ra=a;// const references
int *p=&a;
//ra=3;//error,不能通过ra来修改a的值
a=5;//ok,但是可以用其他方法修改a的值,例如可以直接对变量a赋值
cout<<"通过对变量直接赋值,a="<<a<<','<<"ra="<<ra<<endl;
*p=6;//ok,但是可以通过其他方法修改a的值,例如运用指向a变量的指针变量进行修改
cout<<"通过指向a变量的指针修改a值,a="<<a<<','<<"ra="<<ra<<endl;
system("pause");
return 0;
}
指向const的指针
#include "stdafx.h"
#include <iostream>
using namespace std;
int main()
{
int a=4;
const int*p=&a;//指向const的指针
//*p=6;//error,试图通过指针p来修改a的值
a=8;//ok,虽然不能通过指针p来修改a的值,但是可以通过其他方式修改a的值
cout<<"a="<<a<<','<<"*p="<<*p<<endl;
system("pause");
return 0;
}
编译错误:
1>------ 已启动生成: 项目:test_i, 配置: Debug Win32 ------
1>正在编译...
1>test_i.cpp
1>g:\vs2008project\test_i\test_i\test_i.cpp(9): error C3892: “p”: 不能给常量赋值
1>生成日志保存在“file://g:\VS2008Project\test_i\test_i\Debug\BuildLog.htm”
1>test_i -1 个错误,个警告
========== 生成: 成功0 个,失败1 个,最新0 个,跳过0 个==========
将着色部分注释掉之后,编译和执行信息:
const指针
int a;
int* const p=&a;
直接看程序:
#include "stdafx.h"
#include <iostream>
using namespace std;
int main()
{
int a;
int* constp=&a;
int b=34;
//p=&b;//error,p的指向不能变
*p=12;//ok,虽然p的指向不能变,但是p所指向的内存空间的值是可以修改的。
cout<<"a="<<a<<','<<"*p"<<*p<<endl;
system("pause");
return 0;
}
着色部分发生编译错误:
1>------ 已启动生成: 项目:test_i, 配置: Debug Win32 ------
1>正在编译...
1>test_i.cpp
1>g:\vs2008project\test_i\test_i\test_i.cpp(10): error C3892: “p”: 不能给常量赋值
1>生成日志保存在“file://g:\VS2008Project\test_i\test_i\Debug\BuildLog.htm”
1>test_i -1 个错误,个警告
========== 生成: 成功0 个,失败1 个,最新0 个,跳过0 个==========
将着色部分注释掉之后,编译和执行都正确:
//不能返回局部对象的指针或引用
#include "stdafx.h"
#include <iostream>
using namespace std;
int* right()//ok
{
int *p=new int(45);//堆内存的开辟和回收由程序员手动完成,不同于局部变量
return p;
}
int* wrong()//not ok
{
int a=34;
int *p=&a;
return p;
}
int main()
{
int *p=right();//ok
//int *m=wrong();//not ok不能返回局部对象的指针
cout<<*p<<endl;
system("pause");
return 0;
}
可以使用extern声明一个变量,而不是定义一个变量。extern声明不是定义,也不分配存储空间,它只是说明变量定义在程序的其他地方,程序中变量可以声明多次,但只能定义一次。
任何在多个文件中使用的变量都需要有与定义分离的声明。在这种情况下,一个文件含有变量的定义,使用该变量的其他文件则包含该变量的声明(而不是定义)。
因为头文件包含在多个源文件中,所以不应该含有变量或函数定义。
设计头文件时,应使其可以多次包含在同一个源文件中,这一点很重要,我们必须保证多次包含同一个头文件不会引起该头文件定义的类和对象被多次定义,使得头文件安全通用的做饭,是使用预处理器定义的头文件保护符。
链接的主要内容就是把各个模块之间互相引用的部分都处理好,使得各个模块之间能够正确的衔接。
每个模块的源文件(如.c文件)经过编译器编译成目标文件(如.o或.obj文件),目标和库(Library)一起链接形成最终可执行文件。而最常见的库就是运行时库(RunningLibrary),它是支持程序运行的基本函数的集合。库是一组目标文件的包,就是一些最常用的代码编译成目标文件后打包存放。
动态链接库.dll, .so
静态链接库.lib .a
Gcc –Wall 打开所有的warning提示
Gcc –E 生成预处理文件.i
Gcc –
C语言中没有字符串类型,字符串是存放在字符型数组中的。
C语言是将字符串作为字符数组来处理的。C系统在用字符数组存储字符串常量时会自动加一个’\0’作为结束符。
字符数组初始化的另外一种方法 char ch[]=”helloworld”;
这种加上static声明,只能用于本文件的外部变量称为静态外部(全局)变量。在程序设计中,常由若干人分别完成各个模块,各人可以独立的在其设计的文件中使用相同的外部变量名而互不相干,只需要在每个文件中定义全局变量时加上static即可。
用static声明一个变量的作用是:
(1) 对局部变量用static声明,把它分配在静态存储区,该变量在整个程序执行器件不释放,其所分配的空间始终存在。
(2) 对全局变量用static声明,则该变量的作用域只限于本文件模块(即被声明的文件中)
也就是说,全局变量有两种,一种是静态全局变量,一种是非静态的。其中静态全局变量仅限于当前文件访问。
内部函数加static
数组名代表数组中首元素的地址
C语言没有字符串变量,只有字符变量和字符串常量。
Stdlib.h
String.h
z
C++ sort函数在头文件#include<algorithm>中
C qsort函数在头文件#include<stdlib.h>中
Sort函数接受三个参数sort(pArray,pArray + size);没有第三个参数默认是按照升序排列
Sort(pArray, pArray + size, cmp);第三个参数是cmp比较函数。
bool cmp(int a, int b)
{
Returna > b; //降序
}
而 return a < b 则是升序,也就是默认的。
而c中的qsort相对较为复杂,qsort函数没有返回值,其原型是
qsort(pArray, sizeof(pArray) /sizeof(pArray[0]), sizeof(pArray[0]), cmp);
其中第一个参数带排序数组的指针
第二个参赛是排序的元素的个数
第三个参数是单个元素的大小
第四个参数是cmp比较函数,无论是升序还是降序排序,都需要我们自定义,而自定义cmp函数要求比较苛刻,其原型不能更改,必须为:
Int cmp (const void* a, const void* b)
{
Return*a - *b;
}
上面cmp函数表达的是:如果*a < *b 则*a 排在*b前面,也就是升序排列,如果*a > *b 则*a排在*b后面,因此只要return *a - *b 就表达的是升序,所以降序排列就要这样写:
Return *b - *a;
当然如果是double类型元素比较,可以这样写
Int cmp(consnt void* a, const void* b)
{
Return*(double*)a > *(double*)b ? 1:-1;
}
由于double数字直接相减在转换为Int会损失精度,因此我们自定义了只要double类型的*a 大于 double类型的*b,我们就认为他们相减等于1,否则等于-1。上面的函数的意思也是按照升序排列。
Qsort和sort 函数体里面升序和降序条件刚好是相反的。
//按照字符串升序,其中StrIn是一个结构体
int cmpStr(const void* a, const void* b)
{
return strcmp(((StrIn*)a)->name,((StrIn*)b)->name);
}
#include <iostream>
using namespace std;
#define PI 3.14
int main()
{
#ifdef PI
cout << "define PI, and PI == " << PI << endl;
#else
cout << "not define PI!"<< endl;
#endif
//typedef
typedef char*pChar;
pChar a= "hello world";
pChar b= "good morning";
cout << a << endl
<< b << endl;
system("pause");
return 0;
}
//for typedef
typedef struct Student
{
int num;
char* name;
}Stu,*pStu; //意思是:Stu是struct Student结构体类型的别名,而pStu是struct Student*指针类型的别名
//二级指针(技巧:分清楚值和地址的区别)
int main()
{
int a= 12;
int *b= &a;
int **c= &b;
cout << a << endl; //输出a的值
cout << *b << endl; //输出a的值
cout << **c << endl; //输出a的值
cout << &a << endl; //输出a的地址&a
cout << b << endl; //输出b指针变量的值,也就是a的地址&a
cout << &b << endl; //输出b指针变量的地址,也c的值相等
cout << c << endl; //输出c指针变量的值,也就是b的地址
cout << *c << endl; //c里面存放的是b指针的地址,对b指针的地址解引用,得到的是b中存放的值(不是地址),也就是&a
system("pause");
return 0;
}
//指针数组
int main()
{
const char*keyWord[] = {"cpp","python", "c","javascript"};
cout << sizeof(keyWord)<< endl; //16
cout << sizeof(keyWord[0])<< endl; //4
system("pause");
return 0;
}
#include <iostream>
using namespace std;
int main()
{
//虽然int *pS = p的意思是两个指针指向同一块内存单元,但是目前为止p并没有指向一个真正的内存单元,而是NULL,
//所以即便进行了int *pS = p的操作,当p指针申请了有效的指向内存块后,pS指针并不和它指向相同。
int *p= NULL;
int *pS= p;
p = (int*)malloc(sizeof(int));
*p = 34;
cout << *p << endl;
cout << *pS << endl;//error
}
所以,引出了下面的问题,在链表创建过程中,利用二级指针:要先给*pLinkListHead这个指针申请一块内存空间,然后再将其值赋给另外一个指针pHead,现在pHead和*pLinkListHead指向了同一块内存(链表头)然后就可以操作了。如果一开始就利用错误示范中的写法,先给指针赋值再操作的话,就会出现上面的问题。
//正确代码
void createLinkList(student** pLinkListHead)
{
*pLinkListHead = (student*)malloc(sizeof(student));
if (*pLinkListHead== NULL)
return;
student* pHead = *pLinkListHead;
(pHead)->pNext = NULL;
int iInputAge;
while(cin>> iInputAge)
{
if (iInputAge> 99)
return;
student* pTmp = (student*)malloc(sizeof(student));
pTmp->age = iInputAge;
pTmp->pNext = NULL;
(pHead)->pNext = pTmp;
pHead = pTmp;
}
}
//错误代码
void createLinkList(student** pLinkListHead)
{
//直接给指针赋值,我们的本意是将两个指针指向同一块内存,可是由于目前为止*pLinkListHead是NULL,所以pHead也是NULL,所以二者并没有指向同一块内存,后来对pHead的操作和*pLinkListHead完全没有关系了。
student* pHead = *pLinkListHead;
pHead = (student*)malloc(sizeof(student));
if (pHead== NULL)
return;
(pHead)->pNext = NULL;
int iInputAge;
while(cin>> iInputAge)
{
if (iInputAge> 99)
return;
student* pTmp = (student*)malloc(sizeof(student));
pTmp->age = iInputAge;
pTmp->pNext = NULL;
(pHead)->pNext = pTmp;
pHead = pTmp;
}
}
二级指针和指针的引用很相似,主要目的是想通过形参指针来改变实参指针的指向。
指针的引用(单链表)
#include <iostream>
using namespace std;
//define Studentstruct
struct Student
{
int age;
Student* pNext;
};
//create linkList
void createLinkList(Student*& pHead)
{
pHead = new Student();
pHead->pNext = NULL;
Student* head = pHead;
int inputAge;
while (cin>> inputAge)
{
if (inputAge> 99)
return;
Student* tmp = new Student();
tmp->age = inputAge;
tmp->pNext = NULL;
head->pNext = tmp;
head = tmp;
}
}
//get linkListlength
int lengthLinkList(Student*& pHead)
{
if (pHead== NULL)
return 0;
int lenLinkList= 0;
Student* p = pHead->pNext;
while (p!= NULL)
{
++lenLinkList;
p = p->pNext;
}
return lenLinkList;
}
//delete linkList
void deleteLinkList(Student*& pHead)
{
if (pHead== NULL)
return;
Student* p1 = pHead;
Student* p2 = p1->pNext;
pHead = NULL;
while (p1!= NULL)
{
cout << "delete: " << p1->age<< endl;
delete p1;
p1 = p2;
if (p2!= NULL)
p2 = p2->pNext;
}
}
int main()
{
Student* pHead = NULL;
createLinkList(pHead);
if (pHead== NULL)
cout << "21111111111111" << endl;
cout << lengthLinkList(pHead)<< endl;
deleteLinkList(pHead);
cout << lengthLinkList(pHead)<< endl;
system("pause");
return 0;
}
虽然不建议直接使用==和!=来比较浮点数,但是可以直接比较浮点数谁大谁小,即可将<和>直接应用于浮点数之间的比较及浮点数和整数的比较。
#include <iostream>
using namespace std;
//一维数组作为函数参数传递
void foo_1(int*p, int num)
{
for(inti = 0; i< num; ++i)
{
cout << *(p + i) <<' ';
}
cout << endl;
}
//二维数组作为函数参数()
//m参数表明了行数
void foo_2(int (*p)[3], int m)
{
int row= m;
int col= 2;
for(inti = 0; i< row; ++i)
{
for (intj = 0; j< col; ++j)
{
cout << *(*(p + i) + j) << ' ';
}
}
cout << endl;
}
//二维数组作为函数参数()
void foo_3(int **p, int row, int col)
{
for (inti = 0; i< row; ++i)
{
for (intj = 0; j< col; ++j)
{
cout << *(*(p + i) + j) << ' ';
}
}
cout << endl;
}
int main()
{
int a[10]= {2, 3, 4, 5, 6, 7, 12, 34, 41, 213};
foo_1(a,10);
int b[2][3]= {{2, 4, 5}, {3, 6, 9}};
foo_2(b,2);
int *p[2]= {b[0], b[1]};
int **pp= p;
foo_3(pp,2, 3);
system("pause");
return 0;
}
#include <iostream>
using namespace std;
struct Fru
{
int a[10];
char c;
};
int main()
{
Fru f1,f2;
for(inti=0;i<10;++i)
{
f1.a[i]=i+10;
}
f2=f1;
for(inti=0;i<10;++i)
{
cout<<f2.a[i]<<' ';
}
cout<<endl;
system("pause");
return 0;
}
参见《给函数传递多维数组文档》
//C/C++中许多库函数(strcpy, strcat, strlen等),传递给这些函数的指针必须具有非零值,并且指向以NULL结束的字符数组。
//例子
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
char a[]={'a','b','\0','c','\0','d','\0'};
cout<<strlen(a)<<endl;
cout<<sizeof(a)<<endl;
system("pause");
return 0;
}
//而下面的程序的输出将是不确定的
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
char a[]={'a','b'};
cout<<strlen(a)<<endl; //不确定,因为一直碰到’\0’才算结束
cout<<sizeof(a)<<endl; //确定
system("pause");
return 0;
}
C++规定,在声明和初始化一个二维数组时,如果对二维数组的所有元素都赋值,则第一维(行)可以省略。在C/C++中,二维数组都是按照行优先顺序连续存储的。
int display(int a[][]);//not ok
int display(int a[][3],int m);//ok. M表示传递进去的行数
二维数组可以转换为一维数组的表示形式:a[x][y]=b[x*列数]+y;
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
//二维数组用一维的形式表示出来。注意p的类型是int *,因此p=&a[0][0],不能用p=a或p=&a来表示,因为a的类型为int (*)[3],而&a的类型是int(*)[3][3]类型。但是可以用p=a[0]表示,因为a[0]代表一个数组的地址。
int a[3][3],*p,i;
p=&a[0][0];//ok
//p=a[0];//ok
for(inti=0;i<9;++i)
{
p[i]=i+1;
}
cout<<a[1][2]<<endl;//6
system("pause");
return 0;
}
指针的运算:合法的运算具体包括:指针与整数的加减(包括指针的自增和自减)、同类型指针间的比较、同类型的两指针相减。
所谓指针数组,是指一个数组里面装着指针,也即指针数组是一个数组。
所谓数组指针,是一个指向数组的指针,它其实还是指针,只不过它指向整个数组。一个指向有10个元素整型数组的指针定义为:int (*p)[10];
一维数组int a[10]中a的类型是int *,表示一个数组的首元素的地址,p+1表示下一个元素。而&a的类型是int (*)[10],p+1表示下10个元素。
指针数组
指针数组:是一个数组,只不过里面的元素全是指针。
#include <iostream>
using namespace std;
int main()
{
int *p1=new int();//0
int *p2=new int(34);//34
//p3是一个指针数组,可以从其类型int*看出来。
int* p3[]={p1,p2,p2};
//此时p3并没有退化为指针,只有在数组名作为形参的时候才退化为指针。
cout<<"sizeof(p3)="<<sizeof(p3)<<endl;//12
for(inti=0;i<sizeof(p3)/sizeof(int *);++i)
{
cout<<**(p3+i)<<endl;//*(p3+i)只获得p3数组中第i+1个元素,这个元素是一个地址,因此再次解引用才能获得其指向的真实值。
}
cout<<"int指针输出结束*********"<<endl;
//char指针
char* p4[]={"hello","world","kevin","wang"};
for(inti=0;i<sizeof(p4)/sizeof(char *);++i)
{
cout<<*(p4+i)<<endl;
}
system("pause");
return 0;
}
指针数组的每个元素都是指针变量,指向指针数组元素的指针变量必须是二级指针。
指针数组
定义:
如果一个数组,其元素均为指针型数据,该数组为指针数组,也就是说,指针数组中的每一个元素相当于一个指针变量,它的值都是地址。
形式:
一维指针数组的定义形式为:
int【类型名】 *p【数组名】 [4]【数组长度】;
由于[ ]比*优先级高,因此p先与[4]结合,形成p[4]的数组的形式。然后与p前面的“ * ”结合,“ * ”表示此数组是指针类型的,每个数组元素都相当于一个指针变量,都可以指向整形变量。
注意:不能写成int (*p)[4]的形式,这是一个指向一维数组的指针变量。
使用指针数组中各元素分别指向若干个字符串,使字符串的处理更加灵活。
程序1.1
#include<iostream>
using namespace std;
void sort(char *p[],int n)
{
char *temp;
int i,j,k;
for(i=0;i<n;i++)
{
k=i;
for(j=i;j<n;j++)
{
if(strcmp(p[j],p[k])<0)
{
k=j;
}
}
if(k!=i)
{
temp=p[k];
p[k]=p[i];
p[i]=temp;
}
}
}
void print(char *p[],int n)
{
int i;
for(i=0;i<n;i++)
{
cout<<p[i]<<endl;
}
}
int main()
{
void sort(char *p[],int n);
void print(char *p[],int n);
char *name[]={"C","C++","PHP","ASP","ASP.NET","C#","JAVA","BASIC","PASCAL","COBOL"};
int n=10;
sort(name,n);
print(name,n);
return 0;
}
分析:
在main函数中定义了指针数组name,它的十个元素分别是字符串的起始地址。然后将数组的首元素的地址传到函数sort中p数组中,因此形参p和实参name指向的是同一个数组。然后用选择法对数组进行了排序。
print函数的作用,是输出各字符串,p[0]~p[9]分别是各字符串的首地址。
指向指针的指针
定义:
指向指针数据的指针就是指向指针的指针,例如在程序1.1中的main函数中定义的指针数组name[10],
复制代码代码如下:
char * *p=name
就表示把指针数组的首个指针元素的地址赋给指向指针的变量p;
程序1.2
复制代码代码如下:
#include<iostream>
using namespace std;
int main(){
char *name[]={"C","C++","PHP","ASP","ASP.NET","C#","JAVA","BASIC","PASCAL","COBOL"};
char * *p;
p=name+2;
cout<<p<<endl;
cout<<*p<<endl;//等价于name[2]
cout<<* *p<<endl;
return 0;
}
分析:
p是指向指针的指针,也就是存放的name[2]的地址的值;
*p是指针,也就是name[2]的值(指针数组中的元素);
* *p是指针指向的数据的值,因为定义的p是指向char类型的数据,所以结果输出第一个字符。
动态二维数组
#include <iostream>
using namespace std;
int main()
{
int m=3,n=3;
int** p=new int* [m];
for(int i=0;i<m;++i)
{
*(p+i)=new int[n];
}
for(inti=0;i<m;++i)
{
for(intj=0;j<n;++j)
{
*(*(p+i)+j)=12;
}
}
for(inti=0;i<m;++i)
{
for(intj=0;j<n;++j)
{
cout<<*(*(p+i)+j)<<' ';
}
cout<<endl;
}
for(int i=0;i<m;++i)
{
delete []*(p+i);
}
delete []p;
system("pause");
return 0;
}
指向一维数组的指针和指向二维数组的指针
一维数组a[4];
int*p=a;
二维数组a[3][3];
Int(*p)[3]=a;
参考材料:http://blog.163.com/njut_wangjian/blog/static/16579642520139289599840/
在C++中,数组名代表数组中第一个元素(即序号为0的元素)的地址。因此,下面两个语句等价:
p=&a[0];
p=a;
但是和p=&a;是不同的,虽然三者的值是相同的,都是数组首元素的地址值,但是粒度不同。这时候p的类型为int(*)[10],是一个数组指针;如果用p+1,则跳过整个数组长度。
#include <iostream>
using namespace std;
int main()
{
int a[]={1,2,3,4,5,6};
int *p=a;
for(int i=0;i<sizeof(a)/sizeof(int);++i)
{
cout<<*(p+i)<<' ';
}
cout<<endl;
int *p1=&a[0];
for(int j=0;j<sizeof(a)/sizeof(int);++j)
{
cout<<*(p1+j)<<' ';
}
cout<<endl;
int (*p2)[6]=&a;//p2是数组指针
cout<<sizeof(*p2)<<endl;//p2指向整个数组,因此*p2输出的是整个数组的长度24。
cout<<sizeof(p2)<<endl;//x86下,指针是4
system("pause");
return 0;
}
对于二维数组:以下语句编译通过
int a[2][3]={1,2,3,4,5,6};
int (*p)[3]=a;//ok
因此:对于二维数组a[2][3]来说,其数组名a是int(*p)[3]类型的。如果a+1,则代表越过第一行的3个元素。但是&a是int(*p)[2][3]类型的,&a+1代表跨越整个数组。
//important
#include <iostream>
using namespace std;
int main()
{
int a[6]={1,2,3,4,5,6};
int (*p)[6]=&a;
cout<<sizeof(*p)<<endl;
cout<<*p<<endl;
system("pause");
return 0;
}
一维数组:int a[6];
&a: 是int(*p)[6]类型,&a+1将跳过整个数组
a: 是int*类型,a+1将跳过一个整数。
二维数组:int a[2][3]
&a是int(*p)[2][3]类型,&a+1将跳过整个数组
a:是int(*p)[3]类型,相当于一维数组(因为二维数组就可以看作是很多个一维数组组成的,一行代表一个一维数组),a+1将跳过一行,也就是跳过3个元素。
*(a+i)是int*类型,代表第i行的那个数组的首地址,*(a+i)+j跨越j个int,表示第i行的第j个元素地址。因此*(*(a+i)+j)==a[i][j];
例题:
#include <iostream>
using namespace std;
int main()
{
int a[5]={1,2,3,4,5};
//a是一维数组,&a的类型是int(*p)[5],&a+1将跳过整个数组,也就是跳过20字节
//再强制转换为int*类型。
int *ptr=(int *)(&a+1);
int arr[2][3]={1,2,3,4,5,6};
cout<<(int)a<<endl;//数组名代表数组首元素的地址。
cout<<(int)&a<<endl;//&a代表整个数组的地址,其值和上面一样,但意义不同。
cout<<(int)ptr<<endl;//ptr是数组a跳过整个数组(字节)之后的地址。也就是最后一个元素的后一个地址。
cout<<*(a+1)<<endl;//a[1]
cout<<*(ptr-1)<<endl;//ptr被强制转换为int*型,减之后就表示最后一个元素。
cout<<(int)arr<<endl;//数组名代表数组首元素地址。
cout<<(int)arr[0]<<endl;//array[0]表示第一行一维数组的首地址。
cout<<(int)&arr[0][0]<<endl;//表示数组的首元素地址。
cout<<(int)&arr<<endl;//表示整个数组的地址,&arr是int(*p)[2][3]类型,&arr+1表示跨越整个数组(字节)。
cout<<(int)(arr+1)<<endl;//表示第二行一维数组的首地址。
cout<<(int)(arr[0]+1)<<endl;//表示第一行一维数组的第二个元素的地址。
cout<<(int)(&arr[0][0]+1)<<endl;//表示第一行第二个元素。
cout<<(int)(&arr+1)<<endl;//将跨越整个数组。
system("pause");
return 0;
}
#include <iostream>
using namespace std;
int main()
{
int a[2][3]={0,2,4,6,8,10};
cout<<*(a+1)<<endl;
cout<<**(a+1)+2<<endl;
system("pause");
return 0;
}
因为对于二维数组而言,a[i]表示i+1个行一维数组的首元素地址,而a[i]==*(a+i),因此cout<<*(a+i)<<endl;输出6的地址。而cout<<**(a+1)+2<<endl;则输出6+2=8;
若定义int a[2][3]={0,2,4,6,8,10};则下述正确的有(A,C);【2012中兴】 A *(a+1)为元素6的地址 B *(a[1]+1)的值为2 C **(a+1)+2的值为8 D a[0]与a相同 解析:A,C如上。B:其值应该为8. D:类型不同。
|
解析:
P1的类型是int (*)[4];p1+1表示前进一行。
P2类型是int (*)[4];p2+1表示前进一行。
P3类型是int*类型。
则,*(*(a+1)-1)中a+1表示前进一行,代表第二行,也就是a[1],是int*类型.由于a[1]+1表示前进一个整数元素,因此a[1]-1表示退后一个整数元素,则是50的上一个元素是4.
另外,*(*(p1+3)-2)+1中,p1+3表示前进三行,表示第四行首元素地址,即p1[3],由于p1[3]+n表示本行的第n+1个元素,因此-2表示后退两个元素,是1100,+1之后是1101.
再另外,*(*(p2-1)+16)+2中,p2是int (*)[4]类型,p2-1表示后退一行,(如果类比一下p2+1则表示前进一行,即p2[1],是int*类型),所以*(p2-1)+16表示先后退一行,再前进16个元素,因此是13000,+2后是13002.
再另外,(p3+sizeof(p1)-3))中,在64位编译器中,sizeof(p1)==8,因此p3+5代表前进5个元素,等于60.
指针数组/数组指针,以后两字个为主干。指针数组是一个数组,只不过里面全部是指针。数组指针是一个指针,只不过是指向一个数组的指针。
引用数组/数组的引用,引用数组(如果有的话)应该是int& a[10],但是由于不存在引用的数组,也不存在类似于vector<int&> one这样,因此不存在引用数组。数组的引用:int a[10];int (&p)[10]=a;数组的引用主要用在固定长度的数组实参传递中。
#include <iostream>
#include <algorithm>
using namespace std;
void refint(int (&p)[6])
{
cout<<p[3]<<endl;
}
int main()
{
int a[6]={1,2,3,4,5,6};
int (&p)[6]=a;
refint(p);
system("pause");
return 0;
}
//一维数组的引用
#include <iostream>
#include <algorithm>
using namespace std;
void foo(int (&p)[10])
{
cout<<sizeof(p)<<endl;
copy(p,p+10,ostream_iterator<int>(cout," "));
cout<<endl;
}
int main()
{
int a[10]={2};
foo(a);
system("pause");
return 0;
}
一维数组:
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
int a[5]={1,2,3,4,5};
int *p1=a;//ok
int *p2=&a[0];//ok
//int *p3=&a;//not ok
cout<<*(p1+2)<<endl;
cout<<*(p2+3)<<endl;
system("pause");
return 0;
}
//int*p3=&a;//not ok编译错误提示:
error C2440:'initializing' : cannot convert from 'int (*)[5]' to 'int *'
int (*p)[5]表示:p是一个数组指针,p这个指针指向一个数组,p也称为行指针。
因为int *p3=&a;&a的类型是int (*)[5],这条语句的意思是p3这个指针指向的是一个长度为5的整型数组,而且是以整个数组长度(5)为单位的,并不是以一个整型元素为单位的,比如p3+1表示的是跨越长度为5的整型(或者说是一行)之后的地址,而p1+1或p2+1表示的是a[1]元素的地址,前者跨越整个数组,而后者跨越一个元素。这是因为它们的类型不同。
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
int a[5]={1,2,3,4,5};
int *p1=a;//ok
int *p2=&a[0];//ok
int(*p3)[5]=&a;//注意&a的类型是int(*)[5]
cout<<"p1=="<<p1<<endl;
cout<<"p3=="<<p2<<endl;
cout<<"p3=="<<p3<<endl;
system("pause");
return 0;
}
地址相同,但是意义不同:p1和p2表示的是数组首元素的地址,而p3可以认为是数组的地址。
二维数组
#include <iostream>
using namespace std;
int main()
{
int a[3][3]=
{
{1,2,3},
{4,5,6},
{7,8,9}
};
//声明一个指针用以指向一个二维数组
int (*p)[3]=a;//a的类型是int (*)[3],p用以指向二维数组的一行,也就是一个个的一维数组
for(inti=0;i<3;++i)
{
for(intj=0;j<3;++j)
{
//p[i]或*(p+i)就表示二维数组每一行首元素的地址,*(p+i)不好理解,不过可以从p[i]==*(p+i)来解释
cout<<*(*(p+i)+j)<<' ';// *(p+i)=p[i]
}
cout<<endl;
}
int(*p1)[3][3];
cout<<(int)&a[0][0]<<endl
<<(int)a[0]<<endl
<<(int)a<<endl;
p1=&a;//注意指针的声明方式,&a的类型是int(*p)[3][3]
cout<<(int)(p1+1)<<endl;//跨越整个数组,也就是跨越个元素,数组首地址+36
cout<<"sizeof(*p1)="<<sizeof(*p1)<<endl;
cout<<"sizeof(p1)="<<sizeof(p1)<<endl;
system("pause");
return 0;
}
例一:
#include <iostream>
using namespace std;
int main()
{
int a[3][3],*p,i;
//下面p=&a[0][0],等价于p=a[0],并且p的类型是int* p=&a[0][0];
for(i=0;i<9;++i)
{
p[i]=i+1;
}
cout<<a[1][2]<<endl;
system("pause");
return 0;
}
一个二维数组元素a[x][y]在一维数组b中,是:
a[x][y]=b[x*列数+y];
例二:
#include <iostream>
using namespace std;
int main()
{
int ch[3][3]=
{
{1,2,3},
{4,5,6},
{7,8,9},
};
cout<<ch[1][4]<<endl;
//数组是按照行优先存储的,在内存中占据的是一个连续的空间,因此ch[1][4]实际存取的是ch[2][2],实际我认为是数组越界了,下面刚好有值。
system("pause");
return 0;
}
#include <iostream>
using namespace std;
int main()
{
int a[][3]={1,2,3,4,5,6,7,8,9};
int *p1=&a[0][0];
int (*p2)[3]=a;
int (*p3)[3][3]=&a;
int *p4=a[2];//二维数组是数组的数组,因此a[2]表示第3个一维数组,就像我们定义一个新的数组int b[3]一样,a[2]就是这个被包含的一维数组的地址,当然也可以用&a[2][0]表示这个地址
cout<<*(p1+4)<<endl;
cout<<*(p2[2]+2)<<endl;
cout<<*((int*)(p3+1)-2)<<endl;
cout<<*(p4+2)<<endl;
system("pause");
return 0;
}
总结:
一维数组:a[10]
int *p1=a;
int *p2=&a[0];
int (*p3)[10]=&a;
二维数组:a[3][3]
int *p1=&a[0][0]; //也就是把二维数组转换为一维数组
int (*p2)[3]=a; //每次跨越一行,p2也称为行指针
int (*p3)[3][3]=&a;
int *p4=a[2];
#include <iostream>
using namespace std;
int main()
{
int a[4]={1,2,3,4};
int (&ra)[4]=a;
for(inti=0;i<sizeof(ra)/sizeof(int);++i)
{
cout<<ra[i]<<endl;
}
system("pause");
return 0;
}输出1 2 3 4
如果将加粗的一行代码修改为 int (&ra)[6]=a;则:
1>------ 已启动生成: 项目: explit, 配置:Debug Win32 ------
1>正在编译...
1>main.cpp
1>e:\vs2010 workspace\explit\explit\main.cpp(7): error C2440:“初始化”:无法从“int [4]”转换为“int (&)[6]”
1>生成日志保存在“file://g:\VS2008WorkSpace\explit\explit\Debug\BuildLog.htm”
1>explit - 1 个错误,个警告
========== 生成: 成功0 个,失败1 个,最新0 个,跳过0 个==========
说明数组的长度也是引用类型的一部分,因此可以作为数组的长度。但是这样的局限性比较大,就是只能针对该中类型的数组进行传参。
1. 声明一个指向数组的引用
刚开始接触这个问题的时候确实是有点懵,不过看了C++ Primer后发现这个其实很简单:
int n3[3] = {2, 4, 6}; int (&rn3)[3] = n3; // A reference to an array of 3 ints |
没错,确实是有那么点怪异!
那么,typedef 一下也许就好理解多了:
int n3[3] = {2, 4, 6}; typedef int Int3[3]; Int3& rn3 = n3; // A reference to an array of 3 ints |
2. 定义一个接受数组的引用作为其参数的函数
先看看指针是怎么写的:
void GetInt3_Example1(int* pn3) { pn3[2] = 1; // access the third element } |
不过,谁能保证 pn3 的大小就一定是3个int呢?再试试看:
void GetInt3_Example2(int n3[3]) { n3[2] = 1; // access the third element } |
当然这样只不过是让它看起来正确罢了, 实际上这种写法和第一种没啥区别!试试看:
int loc[2]; // local array of 2 integers GetInt3_Example2(loc); // no compile error, but access violation! |
果然是不行的有木有!
这里还要注意的是,如果改用typedef,会出现一个需要注意的问题:
void GetInt3_Example3(Int3 n3) { Int3 loc = {0}; int n3Size = sizeof(n3); // n3Size == 4 int locSize = sizeof(loc); // locSize == 12, see? } |
所以说,最终还是要用引用才能达到限制大小的目的:
void GetInt3_Example4(int (&n3)[3]) |
或者还是用 typedef 让它看起来舒服些?
void GetInt3_Example5(Int3& n3) |
3. 返回一个指向数组的引用
好了,终于到重点,先来看看用typedef是怎么写的:
Int3& GetInt3_Example6() { static int n3[3] = {2, 3, 1}; return n3; } |
很好,那么如果说不用typedef呢?
好吧,我不得不google之,按照这个博客的解释,原来又是一个怪异的写法:
int (&GetInt3_Example7()) [3] { static int n3[3] = {2, 3, 1}; return n3; } |
真是会让人晕倒啊有木有!
//一维数组的引用
#include "stdafx.h"
#include <iostream>
using namespace std;
int main()
{
int a[]={1,2,3};
const intlength=sizeof(a)/sizeof(int);//length must be const
int (&p)[length]=a;
for(inti=0;i<length;++i)
{
cout<<p[i];
if(i!=length)
{
cout<<' ';
}
}
cout<<endl;
system("pause");
return 0;
}
//指针数组,本质上是数组,只不过数组里面的元素是指针
#include <iostream>
using namespacestd;
int main()
{
char *p[]={"helloworld","good morning","kevin wang"};
for(int i=0;i<sizeof(p)/sizeof(char*);++i)
{
cout<<*(p+i)<<endl;
}
system("pause");
return 0;
}
p+i取得的是数组中第i个单元格的地址,而*(p+i)取得的是该内存单元所存储的值,也就是字符串的地址。
向函数传递一维数组
#include<iostream>
using namespace std;
void foo(int *p,int size)
{
for(inti=0;i<size;++i)
{
cout<<*(p+i)<<' ';
}
cout<<endl;
}
//void foo(intp[],int size)
//{
// for(int i=0;i<size;++i)
// {
// cout<<*(p+i)<<' ';
// }
// cout<<endl;
//}
int main()
{
int suma[]={1,2,3,4,5,6,7,8,9};
foo(suma,sizeof(suma)/sizeof(suma[0]));
system("pause");
return 0;
}
两个foo是等价的,并不是重载,如果同时存在,则编译错误:
1>------ Build started: Project: xu,Configuration: Debug Win32 ------
1>Compiling...
1>main.cpp
1>e:\c++ project\xu\xu\main.cpp(14) :error C2084:function 'void foo(int *,int)' already has a body
1> e:\c++ project\xu\xu\main.cpp(4) : seeprevious definition of 'foo'
1>e:\c++ project\xu\xu\main.cpp(25) :error C3861: 'foo': identifier not found
1>Build log was saved at"file://e:\C++ project\xu\xu\Debug\BuildLog.htm"
1>xu - 2 error(s), 0 warning(s)
========== Build: 0 succeeded, 1 failed,0 up-to-date, 0 skipped ==========
因此,向函数传递一维数组,可以写为以上两种形式的任意一种。
向函数传递多维数组
#include<iostream>
using namespace std;
void foo_01(int p[10][10],int m,int n)
{
for(inti=0;i<m;++i)
{
for(intj=0;j<n;++j)
{
cout<<p[i][j]<<' ';
}
cout<<endl;
}
}
void foo_02(int (*p)[10],intm,int n)//指向数组的指针
{
for(inti=0;i<m;++i)
{
for(intj=0;j<n;++j)
{
cout<<p[i][j]<<' ';
}
cout<<endl;
}
}
void foo_03(int p[][10],intm,int n)
{
for(inti=0;i<m;++i)
{
for(intj=0;j<n;++j)
{
cout<<p[i][j]<<' ';
}
cout<<endl;
}
}
void foo_04(int **p,int m,int n)
{
for(int i=0;i<m;++i)
{
for(int j=0;j<n;++j)
{
//cout<<*(p[i]+j)<<' ';
cout<<*(*(p+i)+j)<<' ';
}
cout<<endl;
}
}
int main()
{
int testa[10][10];
for(inti=0;i<10;++i)
{
for(intj=0;j<10;++j)
{
testa[i][j]=i*2+j;
}
}
foo_01(testa,10,10);
cout<<"foo_01 end."<<endl<<endl;
foo_02(testa,10,10);
cout<<"foo_02 end."<<endl<<endl;
foo_03(testa,10,10);
cout<<"foo_03 end."<<endl<<endl;
//要想用二级指针的形式,那么必须要将原来的二维数组改写为一个指向数组的指针的前提下才可以这样做。
int*p[10];//数组指针
for(int i=0;i<10;++i)
{
p[i]=testa[i];//把二维数组中的每一个一维数组的首地址放到数组指针中
}
//也就是说,int**px=p;p是数组名,也是指针。所以px就是指向指针的指针。
foo_04(p,10,10);
cout<<"foo_04 end."<<endl<<endl;
system("pause");
return 0;
}
解析:其实foo_01/foo_02/foo_03三个函数不能重载,因为他们的本质是一样的,被编译器解释为同样的函数。
比如向foo_01传入参数testb[10][13],则编译错误:
1>------ Build started: Project: xu,Configuration: Debug Win32 ------
1>Compiling...
1>main.cpp
1>e:\c++ project\xu\xu\main.cpp(67) :error C2664: 'foo_01' : cannot convert parameter 1 from 'int [10][13]' to 'int[][10]'
1> Types pointed to are unrelated;conversion requires reinterpret_cast, C-style cast or function-style cast
1>Build log was saved at"file://e:\C++ project\xu\xu\Debug\BuildLog.htm"
1>xu - 1 error(s), 0 warning(s)
========== Build: 0 succeeded, 1 failed,0 up-to-date, 0 skipped ==========
也就是说,数组的第一维参数是没有用的,对于编译器来说“视而不见”。都会被转换为int (*)[10]的参数类型。
动态二维数组
#include <iostream>
using namespace std;
void foo(int **p,int m,int n)
{
for(inti=0;i<m;++i)
{
for(intj=0;j<n;++j)
{
cout<<*(*(p+i)+j)<<' ';
}
cout<<endl;
}
}
int main()
{
int m=4,n=5;
int **pa=new int* [m];
for(inti=0;i<m;++i)
{
pa[i]=new int[n]();
}
foo(pa,m,n);
for(inti=0;i<m;++i)
{
delete [] pa[i];
}
delete [] pa;
system("pause");
return 0;
}
#include <iostream>
using namespace std;
void func()
{
int a[]={1,2,3,4,5};
int *pa[]={&a[0],&a[1],&a[2],&a[3],&a[4],&a[2],&a[3]};
int **ppa=pa;
for(inti=0;i<sizeof(pa)/sizeof(int*);++i)
{
cout<<**(ppa+i)<<endl;
}
}
int main()
{
func();
system("pause");
return 0;
}
在《C专家编程》10.3节的小启发里讲的很透彻:(以下这段文字及对比一定要认真分析!)
数组和指针参数是如何被编译器修改的?
“数组名被改写成一个指针参数”规则并不是递归定义的。数组的数组会被改写成“数组的指针”,而不是“指针的指针”:
实参 所匹配的形参
数组的数组 char c[8][10]; char(*)[10]; 数组指针
指针数组 char*c[10]; char**c; 指针的指针
数组指针(行指针) char(*c)[10]; char(*c)[10]; 不改变
指针的指针 char**c; char **c; 不改变
const引用(常引用)
int a;
const int &ra=a;
用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,从而达到了引用的安全性。
#include "stdafx.h"
#include <iostream>
using namespace std;
int main()
{
int a;
const int &ra=a;// const references
int *p=&a;
//ra=3;//error,不能通过ra来修改a的值
a=5;//ok,但是可以用其他方法修改a的值,例如可以直接对变量a赋值
cout<<"a="<<a<<endl
<<"ra="<<ra<<endl;
*p=6;//ok,但是可以通过其他方法修改a的值,例如运用指向a变量的指针变量进行修改
cout<<"a="<<a<<endl
<<"ra="<<ra<<endl;
system("pause");
return 0;
}
其中着色部分发生编译错误:
1>------ 已启动生成: 项目: test_i, 配置: Debug Win32 ------
1>正在编译...
1>test_i.cpp
1>g:\vs2008project\test_i\test_i\test_i.cpp(10) : error C3892: “ra”: 不能给常量赋值
1>生成日志保存在“file://g:\VS2008Project\test_i\test_i\Debug\BuildLog.htm”
1>test_i - 1 个错误,个警告
========== 生成: 成功0 个,失败1 个,最新0 个,跳过0 个==========
将着色语句注释掉之后,编译和执行正确:
#include "stdafx.h"
#include <iostream>
using namespace std;
int main()
{
int a;
const int &ra=a;// const references
int *p=&a;
//ra=3;//error,不能通过ra来修改a的值
a=5;//ok,但是可以用其他方法修改a的值,例如可以直接对变量a赋值
cout<<"通过对变量直接赋值,a="<<a<<','<<"ra="<<ra<<endl;
*p=6;//ok,但是可以通过其他方法修改a的值,例如运用指向a变量的指针变量进行修改
cout<<"通过指向a变量的指针修改a值,a="<<a<<','<<"ra="<<ra<<endl;
system("pause");
return 0;
}
指向const的指针
#include "stdafx.h"
#include <iostream>
using namespace std;
int main()
{
int a=4;
const int *p=&a;//指向const的指针
//*p=6;//error,试图通过指针p来修改a的值
a=8;//ok,虽然不能通过指针p来修改a的值,但是可以通过其他方式修改a的值
cout<<"a="<<a<<','<<"*p="<<*p<<endl;
system("pause");
return 0;
}
编译错误:
1>------ 已启动生成: 项目: test_i, 配置: Debug Win32 ------
1>正在编译...
1>test_i.cpp
1>g:\vs2008project\test_i\test_i\test_i.cpp(9) : error C3892: “p”: 不能给常量赋值
1>生成日志保存在“file://g:\VS2008Project\test_i\test_i\Debug\BuildLog.htm”
1>test_i - 1 个错误,个警告
========== 生成: 成功0 个,失败1 个,最新0 个,跳过0 个==========
将着色部分注释掉之后,编译和执行信息:
const指针
int a;
int* const p=&a;
直接看程序:
#include "stdafx.h"
#include <iostream>
using namespace std;
int main()
{
int a;
int* const p=&a;
int b=34;
//p=&b;//error,p的指向不能变
*p=12;//ok,虽然p的指向不能变,但是p所指向的内存空间的值是可以修改的。
cout<<"a="<<a<<','<<"*p"<<*p<<endl;
system("pause");
return 0;
}
着色部分发生编译错误:
1>------ 已启动生成: 项目: test_i, 配置: Debug Win32 ------
1>正在编译...
1>test_i.cpp
1>g:\vs2008project\test_i\test_i\test_i.cpp(10) : error C3892: “p”: 不能给常量赋值
1>生成日志保存在“file://g:\VS2008Project\test_i\test_i\Debug\BuildLog.htm”
1>test_i - 1 个错误,个警告
========== 生成: 成功0 个,失败1 个,最新0 个,跳过0 个==========
将着色部分注释掉之后,编译和执行都正确: