指针基础

2015/05/09 C和C++基础

C语言之所以功能强大,很大部分是因为它有灵活的指针运用。

指针可以把它看作一种数据类型,定义指针变量就像定义int、char型变量一样,只不过int型变量存储整数,char型变量存储字符,而指针存储的是内存地址。

9.复杂指针声明

用变量a给出下面的定义:

(1).定义一个整型数;

int a;

(2).定义一个指向整型数的指针;

int *a;

(3).定义一个指向指针的指针,它指向的指针是一个指向整型数的指针;

int **a;

(4).定义一个有10个整型数的数组;

int a[10];

(5).定义一个有10个指针的数组,该指针是指向一个整型数的指针;

int *a[10];

(6).定义一个指向有10个整型数数组的指针;

int (*a)[10];

(7).定义一个指向函数的指针,该函数有一个整型参数并返回一个整型数;

int (*a)(int);

(8).

int (*a[10])(int);

下面通过几个例子来讨论如何来解读复杂指针声明:

int (*func)(int *p);

首先找到未定义的标识符func,它的外面有一对圆括号,而且左边是一个*号,这说明func是一个指针,然后跳出这个圆括号,先看右边,也是一个圆括号,这说明(*func)是一个函数,而func是一个指向这类函数的指针,也就是一个函数指针,这类函数具有int*类型的形参,返回值类型是int。

int (*func)(int *p,int(*f)(int*));

func被一对括号包含,且左边有一个*号,说明func是一个指针,跳出括号,右边也有一个括号,那么func是一个指向函数的指针,这类函数具有形参int*和int(*)(int*),返回值为int类型。f也是一个函数指针,指向的函数具有int*类型的形参,返回值为int。

int (*func[5])(int *p);

func右边是一个[]运算符,说明func是一个具有5个元素的数组,func左边有一个*,说明func的元素是指针,要注意这类的*不是修饰func的,而是修饰func[5]的,原因是[]运算符优先级比*高,func先跟[]结合,因此*修饰的是func[5],也即是func是一个5个元素的指针数组。跳出圆括号,看右边,也是一对圆括号,说明func数组的元素是函数类型的指针,它所指向的函数具有int*类型的形参,返回值类型为int。

int (*(*func)[5])(int *p);

func被圆括号括起,左边有一个*,因此func是一个指针,跳出圆括号,右边是一个[]运算符,说明func是一个指向数组的指针,向左看,左边是*号,说明这个数组的元素时指针,在跳出括号,右边又有一个括号,说明这个数组的元素是指向函数的指针。总结一下,即func是一个指向数组的指针,这个数组的元素是函数指针,这些指针指向具有int*形参,返回值为int类型的函数。

int (*(*func)(int *p))[5];

func是一个函数指针,这类函数具有int*类型的形参,返回值是指向数组的指针,指向的数组元素时具有5个int元素的数组。

指针的运算

指针的元素就是地址的运算,因此指针运算不同于普通变量,它只允许有限的几种运算。除了可把指针指向某一存储单元外,允许指针与整数相加或相减,用来移动指针;允许两个指针相减,可以得到两个地址之间的数据个数;还允许指针与指针或指针与地址之间进行比较,决定指针所指向的存储位置的先后。

10.分析代码写结果–用指针赋值
#include <stdio.h>
#include <stdlib.h>

int main(){
	char a[]="hello world";
	char *ptr=a;
	printf("%c\n",*(ptr+4));   //o
	printf("%c\n",ptr[4]);     //o
	printf("%c\n",a[4]);       //o
	printf("%c\n",*(a+4));     //o

	*(ptr+4)+=1;       //p
	printf("%s\n",a);  //hellp world
	system("pause");
}
11.分析代码写结果–指针加减操作
#include <stdio.h>
#include <stdlib.h>

int main(){
	int a[5]={1,2,3,4,5};
	int *ptr=(int*)(&a+1);
	printf("%d\n",*(a+1));    //2
	printf("%d\n",*(ptr-1));  //5
	system("pause");
}

解析:

对指针进行加1操作,得到的是下一个元素的地址,而不是原有地址值直接加1。所以类型为t的指针的移动是以sizeof(t)为移动单位。ptr是一个int型的指针,(&a+1)即先取a的地址,该地址的值加sizeof(a)的值,即&a+5*sizeof(int),也就是a[5]的地址,显然当前指针已经越过了数组的界限。(int*)(&a+1)则是把上一步计算出来的地址,强制转换为int*类型,并赋值给ptr。

12.分析代码写结果–指针比较
#include <iostream>

using namespace std;

int main(){
	char str1[]="abc";
	char str2[]="abc";
	const char str3[]="abc";
	const char str4[]="abc";
	const char *str5="abc";
	const char *str6="abc";
	char *str7="abc";
	char *str8="abc";

	cout<<(str1==str2)<<" "<<&str1<<" "<<&str2<<endl;  //0
	printf("%0X\n",str1);
	cout<<(str3==str4)<<" "<<&str3<<" "<<&str4<<endl;  //0
	cout<<(str5==str6)<<" "<<&str5<<" "<<&str6<<endl;  //0
	cout<<(str6==str7)<<" "<<&str6<<" "<<&str7<<endl;  //0
	cout<<(str7==str8)<<" "<<&str7<<" "<<&str8<<endl;  //0

	system("pause");
}

程序运行结果:

13.分析代码找错误–内存访问违规
#include <iostream>

using namespace std;

int main(){
	char a;
	char *str1=&a;
	char *str2="AAA";
	strcpy(str1,"hello");   //出错
	cout<<str1<<endl;
	str2[0]='B';            //出错
	cout<<str2<<endl;
	system("pause");
}

str1指向一个字节大小的内存区。由于复制“hello”字符串最少需要6个字节,显然内存大小不够。因此会因为越界进行内存读写而导致程序崩溃。

str2指向“AAA”这个字符常量,因为是常量,所以对str2[0]的赋值操作是不合法的,也会导致程序崩溃。

#include <iostream>

using namespace std;

int main(){
	char str1[]="aaaaaaa";
	char *str2="ABC";
	strcpy(str1,"hello");
	cout<<str1<<endl;     //hello
	cout<<str2[1]<<endl;  //B

	system("pause");
}
14.分析代码找错误–指针的隐式转换
#include <stdio.h>

int main(){
	int ival=1024;
	int ival2=2048;
	int *pi1=&ival;
	int *pi2=&ival2;
	int **pi3=0;

	ival=*pi3;   //编译出错,ival是int型,*pi3是int*类型,不能隐式转换
	*pi2=*pi3;   //编译出错,*pi2是int类型,*pi3是int*类型,不能隐式转换
	ival=pi2;    //编译出错,ival是int型,pi2是int*类型,不能隐式转换
	pi2=*pi1;    //编译出错,pi2是int*类型,*pi1是int类型,不能隐式转换
	pi1=*pi3;    //运行出错,pi3是NULL指针
	ival=*pi1;   //对
	pi1=ival;    //编译出错,pi1是int*类型,ival是int类型,不能隐式转换
	pi3=&pi2;    //对
}
15.指针常量与常量指针的区别

常量指针,即指针是常量形成的,它首先应该是一个指针

指针常量,常量是指针形式,它首先应该是一个常量

常量指针,它是一个指向常量的指针。常量指针指向一个常量,是防止对指针误操作而出现修改常量的错误。因此,常量指针是指向常量的指针,指针所指向的地址内容是不可修改的。

指针常量,它首先是一个常量,然后才是一个指针。指针常量不能修改指针所指向的地址,一旦初始化,地址就固定了,不能对它进行移动操作。如果对指针常量进行自增操作,系统会提示出错。但注意的是,指针常量的内容是可以改变的,这和常量指针是完全不同的概念。总之,指针常量是不可改变地址的指针,但是可以对它所指向的内容进行修改。

总结:

常量指针是指向常量的指针,它所指的地址的内容是不可修改的。

指针常量就是指针的常量,它是不可改变地址的指针,但是可以对它所指向的内容进行修改。

16.分析代码回答问题–指出下列几种指针的区别
char* const p1;   //指针常量,指向地址不可修改,内容可改
char const *p2;   //常量指针,指向地址的内容不可改,地址可改
const char *p3;   //也是常量指针
const char* const p4;  //地址和内容都不可修改
17.找错–常量指针和指针常量的作用
#include <stdio.h>
int main(){
	const char* node1="abc";
	char* const node2="abc";

	node1[2]='k';     //出错
	*node1[2]='k';    //出错
	*node1="xyz";     //出错
	node1="xyz";        //正确 地址改变了
	
	node2[2]='k';       //编译通过 运行错误 因为"abc"是字符串常量
	*node[2]='k';       //出错
	*node2="xyz";       //出错
	node2="xyz";        //出错
}

上面代码中,node1和node2分别是常量指针和指针常量,并且在初始化时都指向了常量字符串“abc”,因此,它们对指向内存的修改都是非法的,如果是node1操作,会出现编译错误,而node2会出现运行错误。

#include <iostream>
#include <stdio.h>

using namespace std;

int main(){

	//node1和node2均指向字符串常量 字符串常量内容不允许改变
	char *node1;
	node1="xyz";             //正确 相当于把字符串常量"xyz"的地址给node1
	char *node2="adb";       //正确
	node2="xtt";             //正确
	node1[1]='k';            //出错
	node2[1]='k';            //出错

	char node3[5];
	node3="xyz";             //出错
	char node4[5]="adf";     //正确 相当于把字符串常量"adf"复制到数组node4中 因此node4中的内容可以改变
	node4[1]='k';            //正确
	cout<<node4<<endl;

	system("pause");
}
C++中this指针

在C++中,对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。This指针的作用域是类内部,在类的非静态成员函数中访问类的非静态成员函数时,编译器会自动将对象地址作为一个隐含参数传递给函数。也就是说,即使没有this指针,编译器在编译的时候也会加上this指针,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。

例如调用:

date.SetMonth(9);

this帮助完成下面代码的转换:

SetMonth(&date,9);

this指针的使用情况说明如下:

1.在类的非静态成员函数中返回类对象本身的时候,直接使用return *this;

2.当参数与成员变量名相同时用来区分二者,如this->n=n;

18.this指针的正确叙述

下列关于this指针的叙述中,正确的是:

A.任何与类相关的函数都有this指针;

B.类的成员函数都有this指针;

C.类的友元函数都有this指针;

D.类的非静态成员函数才有this指针;

友元函数时非成员函数,所以他无法通过this指针进行复制。

19.分析代码写结果–this指针
#include <iostream>

using namespace std;

class MyClass{
public:
	int data;
	MyClass(int data){
		this->data=data;
	}
	void print(){
		//cout<<data<<endl;
		cout<<"hello"<<endl;
	}

};

int main(){
	MyClass *pMyClass;
	pMyClass=new MyClass(1);
	pMyClass->print();          //hello
	pMyClass[0].print();        //hello  0地址就是该对象的地址
	pMyClass[1].print();        //该地址的数据没有初始化 只是把该地址传递给print函数然后执行
	pMyClass[10000000].print();

	system("pause");
}

对于类成员而言,并不是一个对象对应一个单独的成员函数体,而是类的所有对象公用一个成员函数体,程序被编译后,此成员函数地址即已确定。调用类成员函数时,会将当前对象的this指针传递给成员函数。一个类的成员函数整体只有一个,而成员函数之所以能把属于此类的各个对象的数据区别开,是因为每次执行类成员函数时,都会把当前对象的this指针(也就是对象的首地址)传入成员函数,函数体内所有对类数据成员的访问,都会转化为“this->数据成员”的方式

当print函数里没有任何访问对象的数据成员时,此时传进来对象的this指针实际上是没有任何用处的,这样的函数,其特征与全局函数没有太大区别。如果取消注释行,print函数要访问类的数据成员data,而程序只构造了一个MyClass,显然,下标“1”和下标“10000000”的MyClass对象根本不存在,那么对它们的数据成员访问也显然是非法的。

答案:

hello
hello
hello
hello

取消注释行后:

1
hello
1
hello
-33686019
hello
段错误

Search

    Post Directory