在学习指针之前,必须弄清楚“内存单元的地址”和“内存单元的内容”的区别。
指针变量:存储地址的变量,编译系统一般分配存储4个字节的存储单元。
指针:指向一个内存空间,该内存空间存储内容为数据值。比如:
int *a;
int b=5;
a=&b;
其中a为指针变量,该变量存储的是b的地址,也就是指针a指向b空间,b空间存储的内容是数据值5.
指针的指针:指向的内存空间存储的内容为指针值(地址值)
char*(*p)和char **p相等
注意:(1)不能用一个整数给指针变量赋值 (2)定义指针变量要指明类型
指针初始化方式的总结:
1
int a=5;
int *p1;
p1=&a;
int *p2=&a;
2 int *p=NULL 赋值为空指针
3 字符串指针、数组赋值
char *str="hello";
char shrs[]="hello";
char *p[]={"aaa","bbb"}; //字符串指针数组赋值 不可以:int *p[]={1,2};
4 数组的第一个元素的地址赋值给指针
int a[3]={1,2,3};
int *p;
p=&a[0]; //p=a
5 字符串数组应当注意:
#include <iostream>
using namespace std;
int main(){
char *c="abc";
cout<<c<<endl; //打印的不是地址 而是abc
c[1]='A'; //运行过程会报错 因为abc是字符串常量 不可修改
c="ABC"; //得到新的字符串常量地址
cout<<c<<endl; //打印ABC
int a=1;
int *n=&a;
cout<<n<<endl; //打印a的地址
char str[]="adsd";
cout<<str<<endl; //打印adsd
str[4]='a'; //覆盖结束符
cout<<str<<endl; //打印乱码
char *sz[]={"ad","ac","adf"}; //字符串数组 每个元素是一个指针
char **ec=sz; //使用二重指针指向
char **et=new char*[3]; //也可以动态分配二重指针
et[0]="ad";
et[1]="ac";
et[2]="adf";
char rr[2][2];
rr=et; //不能用char** 直接指向char二维数组
system("pause");
}
相关程序1
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
int main(){
int *i;
int a=5;
i=&a;
cout<<i<<" "<<i+1<<endl; //输出相邻的地址
char *c;
char b='c';
c=&b;
printf("%0X %0X\n",c,c+1); //输出相邻地址
int d[2]={1,2};
cout<<d<<" "<<d+1<<endl; //输出相邻地址
cout<<sizeof(d)<<" "<<sizeof(d+1)<<endl; //输出8和4
int *s=d;
cout<<sizeof(s)<<" "<<sizeof(d)<<endl; //输出指针和数组的大小
cout<<&d<<" "<<&d+1<<endl; //输出相邻地址
system("pause");
}
输出结果为:
一维数组
数组定义方式
int n=10;
int a[10];
int b[n]; //报错 n必须为已定义并赋值的常变量 const int n=10;
不合法的定义方式:
int n;
cin>>n;
int a[n];
一维数组的初始化:
(1).全部初始化:int a[5]={0,1,2,3,4}; 或者一部分初始化:int a[5]={0,1,2};
(2).可不指定数组长度初始化:int a[]={1,2,3,4,5};
相关程序2
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
int main(){
int a[10]={1,2,3,4,5,6,7,8,9,10};
int *p;
p=a; //获取数组a第一个元素的地址
for(int i=0;i<10;i++){ //访问方式1(下标法)
cout<<*(p+i)<<endl;
}
for(int i=0;i<10;i++){ //访问方式2(指针法) 等同于*(a+i)
cout<<a[i]<<endl;
}
int *q,*s;
s=a+10;
for(q=a;q<s;q++){ //访问方式3(指针变量指向数组元素) 相对来说 3比1和2要快
cout<<*q<<endl;
}
cout<<*p++<<endl; //*(p++) 输出1 p指向第二个元素
cout<<*++p<<endl; //*(++p) 输出3 p指向第三个元素
cout<<(*p)++<<endl; //输出3
cout<<++(*p)<<endl; //输出5
system("pause");
}
数组名与函数参数
(1).用数组名作函数参数,传递的是数组首元素的地址;
void sort(int arr[10]){
cout<<sizeof(arr)<<endl; //输出4
}
int main(){
int arr[10];
cout<<sizeof(arr)<<endl; //输出40
sort(arr);
}
(2).用指针变量作函数形参,同样可以接收从实参传递过来的数组首元素的地址,此时,实参为数组名。
void sort(int *arr){
cout<<sizeof(arr)<<endl; //输出4
}
int main(){
int arr[10];
cout<<sizeof(arr)<<endl; //输出40
int *arr0;
arr0=(int*)malloc(10*sizeof(int));
cout<<sizeof(arr0)<<endl; //输出4
sort(arr);
}
二维数组
二维数组定义与初始化
二维数组定义,例如:
float a[3][4];
//其中a[0],a[1],a[2]都是数组名,代表一维数组,可以转换为指针,等同于*(a+i);
//a[0][0],a[0][1],a[0][2],a[0][3]代表一维数组a[0]的元素,等同于*(*(a+i)+j);
//一维数组a[1],a[2]和a[0]一样;
//把a看作一维数组,则该数组中的每个元素都是一个数组;
三维数组定义:
float a[2][3][4];
二维数组初始化:
//直接初始化
int a[3][4]={\{1,2,3,4},{5,6,7,8},{9,10,11,12}\};
int b[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
int c[3][4]={\{1},{5},{9}\};
//初始化结果为:
// 1 0 0 0
// 5 0 0 0
// 9 0 0 0
int d[3][4]={\{1},{0,6},{9,0,11}\};
//初始化结果为:
// 1 0 0 0
// 0 6 0 0
// 9 0 11 0
//第一维度可省,第二维度不可以省
int e[][4]={\{0,0,3},{},{0,10}\};
使用int**创建二维数组:
#include <iostream>
using namespace std;
int main(){
const int n=3;
int **a=new int*[n]; //创建n行指针数组
for(int i=0;i<n;i++)
a[i]=new int[n]; //n列
int sum=1;
for(int i=0;i<n;i++){ //赋值
for(int j=0;j<n;j++){
a[i][j]=sum;
sum++;
}
}
for(int i=0;i<n;i++){ //打印
for(int j=0;j<n;j++){
cout<<a[i][j]<<" ";
}
cout<<endl;
}
system("pause");
}
二维数组与函数
数组元素作实参,用法和普通变量一样;
数组名作函数实参:
void select_sort(int array[],int n);
当实参为数组名时,形参也应该为数组名或指针变量,数组名代表数组首元素的地址。
注意:在用变量作函数参数时,只能將实参变量的值传给形参变量,在调用函数过程中如果已经改变了形参的值,则对实参没有影响,即实参的值不因形参的值改变而改变。而数组名作函数实参,改变形参也就改变实参,因为数组名传递给函数形参是一个指针。
形参一维数组的声明中可以写元素的个数,也可以不写。
二维数组名作为实参和形参,必须指定第二维的大小,第一维的大小可以不指定:
int arr1[2][10];
int arr2[][10];
在第二维大小相同的前提下,形参数组的第一维可与实参数组不同:
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
void printshuzu(int arr[2][10]){
for(int i=0;i<3;i++){
for(int j=0;j<10;j++){
cout<<arr[i][j]<<" ";
}
cout<<endl;
}
}
int main(){
int a[3][10];
for(int i=0;i<3;i++){
for(int j=0;j<10;j++){
a[i][j]=j;
}
}
printshuzu(a);
system("pause");
}
一维数组强转为二维数组指针:
#include <iostream>
using namespace std;
int main(){
int a[6]={1,2,3,4,5,6};
int (*p1)[6]=&a;
int (*p2)[3][2];
p2=(int (*)[3][2])p1; //一维数组指针强转为二维数组指针
system("pause");
}
#include <iostream>
using namespace std;
int main(){
int a[6]={1,2,3,4,5,6};
int *p3=a;
int (*p4)[3][2];
p4=(int (*)[3][2])p3;
system("pause");
}
字符数组
字符串数组初始化:
char a[10]={'I',' ','a','m',' ','h','a','p','p','y'};
char b[]="I am happy";
//两者初始化并不等价,b后面会自动添加'\0'结束符
//二维字符数组初始化
char diamond[3][5]={"dad","dggg","sdw"};
//二者等价
char str1[]={"I am happy"};
char str2[]="I am happy";
//二者不等价
char str3[]={'I',' ','a','m',' ','h','a','p','p','y'};
char str4[]={'I',' ','a','m',' ','h','a','p','p','y','\0'};
只能对字符数组的元素进行赋值,而不能用赋值语句对整个数组进行赋值。
字符串数组与指针:
1.数组存放一个字符串:
char str[]="I love China";
2.字符串变量存放字符串:
string str="I love China";
3.字符指针指向一个字符串:
char *str="I love China";
函数指针
1.用函数指针变量调用函数
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
int max(int x,int y){
if(x>=y)
return x;
else
return y;
}
int main(){
int (*p)(int,int);
p=max; //赋值函数入口地址
cout<<p(4,6)<<endl;
system("pause");
}
2.用指向函数的指针作为函数的参数:
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
int shijinzhi(int num){
printf("%d\n",num);
return 1;
}
int shiliujinzhi(int num){
printf("%0x\n",num);
return 1;
}
void print(int num,int (*fun)(int)){
fun(num);
}
int main(){
int num=13;
print(num,shijinzhi);
print(num,shiliujinzhi);
system("pause");
}
3.返回指针值得函数:
int *a(int x,int y); //返回一个整型变量的指针
各种总结
1.无论是一维数组还是二维数组,数组名可表示为首元素的地址,它可转换为指向第一个元素的指针。数组名与指针并不等价。
int a[4]={1,2,3,4};
cout<<sizeof(a)<<endl; //4*4=16
int *p=a; //指针p指向数组a的第一个元素,跟int *p=&a[0]是等价的
//并且可以通过指针p访问数组,因为数组的存储是顺序排列的
cout<<sizeof(p)<<endl; //4
2.指针数组
int a[4]={1,2,3,4};
int b[5]={1,2,3,4,5};
int *p1[2]; //声明一个指针数组,数组每个元素是一个指针,数组大小为2
p1[0]=a; //等价于p1[0]=&a[0]
p1[1]=b; //等价于p1[1]=&b[0]
3.数组指针
int a[4]={1,2,3,4};
int (*p2)[4];
p2=&a;
cout<<a<<" "<<&a<<endl; //输出一样的值
cout<<sizeof(*p2)<<endl; //输出16
//声明一个数组指针,该指针专门指向一个大小为4的数组
//*p2代表数组名,使用(*p2)[i]或者p2[0][i]即可访问数组
//p2指向数组a,&a表示取出[0]数组a的地址
最后一个输出语句的输出结果是相同的,但代表的意义却不大相同。其中a代表数组a第一个元素的地址,而&a代表数组a的地址,
例如:
cout<<a<<a+1<<endl; //输出的两值相差4
cout<<&a<<&a+1<<endl; //输出的两值相差16
int c[4][4]; //声明一个二维数组
//c[0],c[1],c[2],c[3]都是一维数组的数组名,相当于*(c+i)
//c[0][0],c[0][1]...都是一维数组c[0]的元素,相当于*(*(c+i)+j)
int (*p2)[4];
p2=&c[0];
//p2指向一维数组c[0],等价于p2=c
//因为数组名c就是第一个元素c[0]地址
//因此c可转换为一个指向大小为4的数组指针 (*p)[4]
//p2[i][j]即可访问数组
//不能把数组名c赋值给指针的指针**p
4.引用
int a;
int &b=a; //在声明一个引用类型变量时,必须同时使之初始化
int (&p)[4]; //p是引用,它引用4个int型元素的数组
int c;
&b=c; //在执行期间不能再作为其他变量的引用
5.指针的指针用法:
(1).调用函数中需要修改指针参数,可返回修改的指针:
GetMemory(char **p,int num);
(2).可动态申请二维数组;
(3).指向一个指针数组:
int a1[3]={2,3,3};
int a2[3]={5,5,6};
int a3[3]={7,1,4};
int a4[3]={1,8,9};
int *name[]={a1,a2,a3,a4}; //指针数组
//name为指针数组第一个元素的地址,其类型为地址的地址(指针的指针)
int **p;
p=name; //p[0]=a1; p[1]=a2; p[2]=a3; p[3]=a4;
指针题目总结
1、以下代码运行不会发生错误,且输出结果为(hell)的有:
A) char buf[4]="hell";
printf("(%s)",buf);
B) char buf[]="hell";
printf("(%s)",buf);
C) char buf[8]={0};
memcpy(buf,"hello",strlen("hell"));
printf("(%s)",buf);
D) char buf[4];
strcpy(buf,"hell");
printf("(%s)",buf);
答案: B、C
其中A和D都忽略了字符串结束符’\0’,同时要注意字符串结束符’\0’的编码值就是整数0。
2、以下代码的执行结果是:
enum digit{ONE,TWD,THREE};
enum color{RED=2,BLACK};
printf("%d,%d",ONE,BLACK);
A) 0
B) 1
C) 2
D) 3
E) 不确定
F) 以上都不是
答案: A、D
解析:
#include <iostream>
using namespace std;
int main(){
char c='\0';
int n=c;
cout<<n<<endl; //0
bool b=-2;
cout<<b<<endl; //1
if(b)
cout<<"yes"<<endl; //yes
else
cout<<"no"<<endl;
enum str{s1,s2,s3,s4=100,s5,s6};
str ss=s2;
cout<<s1<<" "<<s2<<" "<<s3<<" "<<s4<<" "<<s5<<" "<<s6<<endl;
system("pause");
}
3、以下代码的运行结果是:
#define ADD(arr,cnt) \ //'\'为续行符
do{ \
int i; \
for(int i=0;i<cnt;++i){ \
ret+=arr[i]; \
} \
}while(0)
int a[2][2]={1,1};
int i,ret=0;
for(i=0;i<2;++i){
ADD(a[i],2);
}
printf("%d",ret);
A) 4
B) 2
C) 1
D) 0
E) 都不是
答案: B
4、以下代码运行结果是:
struct{int a,b;} t={1};
printf("%d",t.a/t.b); //运行错误
void func(){
int a;
printf("%d",a); //运行错误
}
void func(){
static int a; //0
printf("%d",a);
}
int a;
void func(){
printf("%d",a); //0
}
A) 1
B) 0
C) 0.1
D) 不确定
E) 错误
F) 以上都不是
答案: E、E、B、B
5、下述代码输出结果是:
int i1=0xf0000000;
unsigned int i2=0xf0000000;
i1>>=1;
i2>>=2;
printf("%08x,%08x",i1,i2);
A) f8000000
B) 78000000
C) 70000000
D) f0000000
答案: A、B
解析:把一个二进制数右移N位,规则为:
除符号位外,全部右移N位,
如果数字是一个无符号数值或是有符号正数,则用0填补最左边的N位;
如果数字是一个有符号数值且是负数,则用1填补最左边的N位。
也就是说如果数字原先是一个正数,则右移之后在最左边补N个0;如果数字原先是个负数,则右移之后在最左边填补N个1。
例子:
0000 0010 » 1 = 0000 0001
0000 1010 » 2 = 0000 0010
1000 0010 » 1 = 1100 0001
1000 1010 » 3 = 1111 0001
负数在计算机内部是以补码形式存放的,
1000 0010是一个负数,它的原码是1111 1110,(原码、反码、补码怎么转换自行百度),也就是说它不是表示-2,而是表示-126;
怎么证明1000 0010右移1位后是1100 0001呢,
1000 0010的原码是1111 1110,对原码右移1位,为1011 1111,(也就是-63),再换成补码,为1100 0001,
可以看出,1000 0010右移1位确实是1100 0001,也就是说,
如果数字原先是个负数,则右移之后在最左边填补N个1。
6、下述代码输出结果是:
int i1=2; //1
i1=!!i1;
int i2=-1; //0
if(i2) i2=0;
else i2=1;
int i3=3||2&&0; //1 先计算&& 再计算|| 优先级问题
int i4=(3 & 5) ? 0 : 1; //0;
A) 3
B) 0
C) 1
D) 2
答案:C、B、C、B
7、下述代码输出结果是:
signed char c1=-1; //-1
int i1=(int)c1;
unsigned char c2=-1; //255
int i2=(int)c2;
int i3=(int)(3*1.0/2*3); //4
printf("%d,%d,%d",i1,i2,i3);
A) -1
B) 255
C) 256
D) 3
E) 4
答案: A、B、E
解析:
#include <iostream>
using namespace std;
int main(){
signed char c1=-1; //-1
int i1=(int)c1;
unsigned char c2=-1; //255
int i2=(int)c2;
int i3=(int)(3*1.0/2*3); //4 '*'和'/'是同一优先级 从左至右运算 1.0是double型 所有和1.0运算的int提升为整型
int i4=(int)(3*1/2*3); //3
printf("%d,%d,%d,%d\n",i1,i2,i3,i4);
signed int i5=-1;
unsigned int i6=i5; //i6应该为 1111 1111 1111 1111 1111 1111 1111 1111
printf("%d ",i6); //d以有符号数去解析
printf("%u ",i6); //u以无符号数去解析
cout<<i6<<endl; //因为i6为无符号 所以cout自动以无符号去解析
signed short i7=-1; //-1
int i8=(int)i7;
cout<<i8<<endl;
unsigned short i9=-1; //65535
int i10=(int)i9;
cout<<i10<<endl;
system("pause");
}
8、下述代码输出结果是:
void fun(int *ptr,int arr[],int val){
*ptr=3;
*arr=3;
val=3;
}
int main(){
int arr[3]={1,2};
fun(arr,&arr[1],arr[2]);
printf("%d,%d,%d\n",arr[0],arr[1],arr[2]); // 3 3 0
}
A) 0
B) 1
C) 2
D) 3
E) 都不是
答案: D、D、A
9、下述代码输出结果是:
int a[6][2]={1,2,3,4,5,6,7,8,9};
int *p=a[1]; //3
int (*q)[2]=&a[1]; //3
printf("%d,%d,%d",*++p,q[0][0],q[1][2]); //4 3 7
A) 0
B) 3
C) 4
D) 5
E) 6
F) 7
G) 出错
答案: 4、3、7
解析:
二维数组在内存中的分配方式:
#include <iostream>
using namespace std;
int main(){
char a[3][2];
for(int i=0;i<3;i++){
for(int j=0;j<2;j++){
printf("%0X ",&a[i][j]);
}
printf("\n");
}
system("pause");
}
#include <iostream>
using namespace std;
int main(){ //移位
int a1=0x80000000;
a1=a1>>1;
printf("%0x\n",a1); //f8000000
unsigned int a2=0xf0000000;
a2=a2>>1;
printf("%0x\n",a2); //78000000
struct s{ //结构体部分赋值
int a;
int b;
};
s st1={1};
cout<<st1.a<<" "<<st1.b<<endl; //1 0
s st2;
//cout<<st2.a<<" "<<st2.b<<endl; //未初始化 运行出错
int b1[2][2]={1,1,2}; //一经赋值 其他值为0
int a[5]={}; //全都初始化为0
int b2[2][2]={\{1,1},2};
int b3[2][2]={1,1}; //列为主元
int b4[2][2]={1}; //int b4[2][2]=1 是错的
int c[2][2]; //没有初始化 都为不确定值
int d1[6][2]={1,2,3,4,5,6,7,8};
int *d2=&d1[1][1];
d2++;
cout<<*d2<<endl; //打印5
int (*d3)[2]=&d1[2];
cout<<*d3[0]<<endl; //打印5
int d4[6]={1,2,3,4,5,6};
int *d5=d4;
int (*d6)[2][3]=(int (*)[2][3])d5; //强行转换为二维
for(int i=0;i<2;i++){
for(int j=0;j<3;j++)
cout<<*d6[i][j]<<" ";
cout<<endl;
}
system("pause");
}
10、已知如下代码,请说明这些指针分别指向什么位置:
void func(char a){
static char b; //静态变量默认初始化为0
char *p1=&a;
char *p2=&b;
char p3[]="&func";
}
static void (*p4)(char)=&func; //程序的入口地址应该存在代码段吧!!!
A) 数据段
B) 代码段
C) 栈
D) 堆
E) 不一定,视调用情况而定
答案: p1:C、p2:A、p3:C、p4:B
解析:
对任何一个普通进程来讲,它都会涉及到5种不同的段:
BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。
数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量和静态变量的一块内存区域。数据段属于静态内存分配。
代码段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)
栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。它是由操作系统分配的,内存的申请与回收都由OS管理。
11、已知32位x86平台,下述代码输出结果是:
void func(char str1[8]){
const char *str2="hello";
char str3[]="he";
printf("%d,%d,%d,%d",sizeof(str1),sizeof(str2),sizeof(str3),sizeof("hello"));
}
A) 2
B) 3
C) 4
D) 5
E) 6
F) 8
答案: C、C、B、E
12、下述代码的输出结果是:
int get(int val){
static int s1,s2=1;
if(val>0)
s1=val;
return s1+s2;
}
printf("%d,",get(0)); //1
printf("%d,",get(2)); //3
printf("%d",get(0)); //3
A) 0
B) 1
C) 2
D) 3
E) 4
F) 都不是
答案:B、D、D
13、32位平台,4字节对齐,下述代码输出结果是:
struct{
int val1;
int val2;
}a={1};
int *p=(int*)&a;
printf("%d,%d",p[0],p[1]);
A) 0
B) 1
C) 2
D) 不确定
答案: B、A
13、32位x86平台,4字节对齐,下述代码输出结果是:
union uni{
struct{
int n3;
}s1;
struct{
int n4;
int n5;
}s2;
};
int main(){
union uni u;
int *p=&u.s2.n4;
memset(&u,0,sizeof(u));
u.s1.n3=1;
u.s2.n4=2;
u.s2.n5=3;
*p=4;
printf("%d,%d,%d\n",u.s1.n3,u.s2.n4,u.s2.n5);
system("pause");
}
A) 0
B) 1
C) 2
D) 3
E) 4
F) 都不是
答案:E、E、D