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
段错误