指针基础
-
变量的地址 int x = 100;
整型变量 x 存储在内存的某个位置上,这个位置就是变量的地址。
用来表示和存储变量地址的量就是函数指针。
例如在 32 位(或 64位机器上 x86 平台)上其地址可能为 0x00394768,则 x 引用这个位置。 -
地址操作符
要获取某个变量的地址,需要使用取址操作符——&。
int c;
cout << &c << endl;//请忽略这里借用 C++ 输出方式,纯 C 可用下面输出
// printf("%08x\n", &c);
可以看出其地址为 003AF77C(不同平台不同次运行操作结果不一样)。
& 可以作用于任何变量名,包括但不限于 int , long ,float ,struct 等等等,例如:
struct person {
char firstLetter;
int age;
double salary;
bool sex;
struct person *next;
};
int main()
{
struct person managerJC;
cout << &managerJC << endl;
cout << &managerJC.firstLetter << endl;
cout << &managerJC.age << endl;
cout << &managerJC.salary << endl;
cout << &managerJC.sex << endl;
cout << &managerJC.next << endl;
getchar();
return 0;
}
在 Windows x86 平台下
输出如下:第一个 char 元素的地址不可获取,其他都要按规定对齐!
(char 只占1个字节,所以取不了地址,只能对大于等于4比特的取址,若要对 char 变量取地址,可以 static_cast 强制转化为 (void *)来求)
0093F824
烫烫烫烫烫烫烫烫烫烫烫烫烫烫&
鴵
0093F828
0093F82C
0093F834
0093F838
它们十分接近,故结构体内的成员字段,地址按顺序或依据大小就近分配。
附注:结构体空间大小计算
结构体占用空间
要看平台 x86 x64 还是 arm,还看操作系统?
一个结构体变量在内存中占用的字节数的计算方法:1. 结构体变量的各成员是按顺序存储的,但不一定是连续存储的。2. 每个成员在结构体变量中的存储位置(相对于结构体变量的首地址的地址),必须是其对齐模数的整数倍。3. 整个结构体变量在内存中占用的字节数,必须是所有成员的对齐模数的最大值的整数倍。
struct X
{
char a;
float b;
int c;
double d;
unsigned e;
};
struct X
{
char a;
float b;
int c;
double d;
unsigned e;
int *f;
};
struct X
{
char a;
float b;
int c;
double d;
unsigned e;
int *f;
double *g;
};
指针声明
存储数据地址的变量叫做指针变量
声明方式:
数据类型 *变量名;
int *pa;
char *pc;
float *pf1,*pf2;
指针赋值
指针声明时没有没有指向。
两种指针赋值的方式:
- 利用取地址符号分配变量的地址;
int x = 100;
int * px;
px = &x;
让指针变量指向来自堆的动态内存分配;
int *pa;
pa = (int *)malloc(sizeof(int)*count);
警告:
任何指针变量的操作都必须在指向有效内存地址的前提下完成,否则会引起 segmentation fault(访问冲突)。
-
指针变量大小
这与平台有关,32位平台指针变量占 4 比特,64位平台指针变量占 8 比特,而与所指向的变量类型无关。 -
指针解引用
使用“取值”操作符——* 访问该地址存储的值称为指针的解引用。
int x = 100;
int px = &x;
cout << px << endl;
cout << *px <<endl;//解引用
注意:
必须在解引用前使指针变量指向特定内存地址。
(你有考虑过全局变量指针和局部变量指针吗?)
全局变量指针有默认初始值 null,编译不会报错,运行时 null 解引用会报错。局部变量指针没有初始值,编译时就会报错。
函数参数使用指针变量的优势
值传递
值传递,会将参数复制到被调用函数相应的栈中,这会占用大量空间。
同时不能对参数的值进行修改。
引用传递
这是传地址,将变量地址传给被调用函数。
此时传递的数据量就是地址所占字节,在传递用户自定义结构的时候往往在传递的信息量相同时减少复制的信息量。
可以对参数的值进行修改。
指针和常量
-
指针常量变量
仅指向唯一内存地址的指针变量,确定后不能修改。
<指针类型 *> const <变量名>
1
声明时必须初始化。
一旦初始化,不能再指向其他内存地址。
int num1=100,num2=500;
int * const ptr1 = &num1;
ptr1 = &num2;//error,can not do this! -
常量指针变量
不能修改所指向的变量的值的指针
const <指针类型 *> <变量名>
int num1=100;
const int *ptr1;
ptr1 = &num1;
*ptr1 = 20;//compile error,expression must be adjustable left-value
试图通过常量指针变量修改所指向变量的值会引起编译错误。
- 常量指针常量变量
上面俩的综合
const <指针类型 *> const <变量名>
多级指针
- 指向指针变量的指针——指针变量指针
<数据类型> **<变量名>
型号的数目取决于间接引用的级数,随着星号数目的增加间接引用的级数也会增加。
int val = 100;
int *ptrval = &val;
int **ptrptrval = &ptrval;
//ptrval == &val
//*ptrval == val == *(&val)
//ptrptrval == &ptrval
//*ptrptrval == ptrval == &val
//**ptrptrval == *ptrval == val
指针运算与一维数组
数组内存排列
int array[32];
//array == &array[0]
连续的数组索引其内存地址也是连续的,数组名 array 为首个元素地址,之后每个元素的地址位置是前一个元素地址位置加上数据类型的字长。
- 字节序
描述数据在内存中的排列格式,主要有大端和小端两种,它和处理器平台有关。(跨平台要考虑这个问题) - 大端
高字节存储在第一个位置。 - 小端
低字节存储在第一个位置。
问题:
你会写一个程序来判断你的处理器是大端还是小端吗?(按定义)
int a = 1;
int b = (char *)&a;
if(b == 1)
cout<<“LITTLE_ENDIAN”<<endl;
else
cout<<“BIG_ENDIAN”<<endl;
指针运算
int a[100];
int *pa=&a;
pa++;//
//a++ error ,a is not an adjustable left-value
pa–;
pa=pa+1;
pa=pa-1;
指针加减法
加法运算后指针变量指向的地址取决于由其数值与数据类型空间大小相乘表达式的计算值。例如:
假如 pa 指向 0x8000;
pa + 1 则指向 0x8004,而不是 0x8001!
也即调到下一个整数的位置(4字节),而不是下个字节的位置。
这是因为,编译器编译时做以下转换:
<指针变量> = <指针变量> + <增加值>;
到:
<指针变量> = <指针变量> + <增加值>*<指针所指变量数据类型大小>;
警告:
禁止将数组变量名指向其它值,也即数组变量名相当于 int *const array;
指针减法也类似,只是两个指针变量不能相加,却可以相减,表示两个指针之间元素的个数(非地址的个数)。
数组探究
index
int array[100];
int pa=array;
//array==&array[0];
//array+offset==&array[offset];
//(array+offset)==array[offset];
动态数组
有的时候定义时不知道要存储的数组元素数目。这时候就要借助malloc在堆上分配了。
int *pa=(int *)malloc(sizeof(int));
指针数组
一个数组,每个元素都是指针
<数据类型 *><变量名>[数组元素数目]
int array[100];
int *pa[100];
for(int i=0;i<100;i++){
pa[i]=array+i;
}
数组指针
指向数组的指针变量
<数据类型>(<变量名>)[数组元素数目];
int ( ptr2arr)[4];
问题:
你造下面的输出分别是啥吗?
int n[][3] = { 10,20,30,40,50,60 };
int (*p)[3];
p = n;
cout << p << endl;
cout << p[0] << endl;
cout << &p[0][0] << endl;
cout << *p[1] << endl;
cout << **(p+1) << endl;//jump 多远的问题
cout << *p[2] << endl;
cout << p[0][0] << endl;
cout << *(p[1] + 1) << endl;
cout << (*p)[2] << endl;
cout << (*p)[3] << endl;
指针与字符串
内存中的字符串
在 C 中,字符串 “abcdefg”尾部默认补‘\0’表示字符串结尾。
char *strptr = “Hello”;
char strarray1[] = “Hello”;
char strarray2[6] = “Hello”;//strarray2[5] 则会在编译时报错,长度不够存储
char strarray3[4] = { ‘a’,‘b’,‘c’,‘d’ };
strlen() 返回字符串的长度,不包括‘\0’。
动态内存分配
和数组类似,但是要注意多分配一个‘\0’。
字符串文本和常量
-
分析下面两个程序
#include
using namespace std;
int *foo(void);
int main()
{
int *m = foo();
cout << *m;
getchar();return 0;
}
int *foo(void) {
int i = 100;
return &i;
}
这段程序据说有可能打印出10,也有可能是未知值。目前我只打印出10,总之这个要考虑的是调用时函数栈的声明周期。
#include
using namespace std;
char *foo(void);
int main()
{
char *m = foo();
cout << m;
getchar();
return 0;
}
char *foo(void) {
char *i = “hello”;
return i;
}
这个就始终打印“hello”了,因为字符串文本或常量是从RO(read only)扇区分配,生命周期是整个程序运行时。
也因为在RO区,所以此指针相当于 const char*变量名,可以修改指向,但不能修改值
strs[0]=‘1’;//运行时出错!冲突
字符串操作
字符串输入
char m=(char)malloc(sizeof(char)*10);
cin >> m;//scanf("%s",m);
字符串遍历&求长度
int length = 0;
while(*strptr != ‘\0’){
//blablabla
strptr++;
length++;
}
字符串复制(RO区能这样做吗)
void str_copy(char *dest_str,const char *src_str){
char *stemp = src_str;
char *dtemp = dest_str;
while( *stemp != ‘\0’ ){
*dtemp = *stemp;
stemp++;
dtemp++;
}
*dtemp=’\0’;
}
字符串连接(RO区能这样做吗)
void str_cat(char * deststr,const char *srcstr){
char *dtemp =deststr;
char *stemp = srcstr;
while(*dtemp != ‘\0’){
dtemp++;
}
while(*stemp != ‘\0’){
*dtemp = *stemp;
dtemp ++;
stemp ++;
}
*dtemp = ‘\0’;
}
字符串数组声明
1.二维数组
用于字符串个数和长度都已知的情况下
char arr[6][10]={“aaa”,“bbb”,“ccc”,“ddd”,“eee”,“fff”}
2.指针数组
适用于个数已知,长度未知的情形。
char *arr[6];//6个字符串,每个长度未知。
//注意这时要malloc分配每个字符串空间,同时用完要free掉
3.二级指针
适用于长度和位置都未知的情形。
char **arr;
//这时就要首先分配 arr,然后对每个元素arr[i]再进行分配。
//释放内存时,也要先从每个元素arr[i]释放,最后释放arr。
指针和多维数组
本质上多维数组在内存中是连续的。