数据的存储
- 计算机将内存分隔为字节(byte),每个字节可存储8位(bit)的二进制信息。每个字节都有唯一的地址,用来和内存中其他字节相区别。一个字节称为一个存储单元(内存单元)。
- 为了正确访问内存单元,每个内存单元都有一个编号,内存单元的编号称为地址。
- 内存单元中的内容是我们关注的数据
访问方式
- 数据访问分为直接访问和间接访问
- 直接访问指的是直接访问内存单元中的内容,间接访问时通过内存单元编号(地址)以及数据所占字节数来访问内存中的数据。
- 间接访问在程序中随处可见,指针是间接访问的常用方式。
指针变量
- 理解指针的第一步是在机器级别上观察指针表示的内容,指针是内存中数据的内存地址。
- 指针就是地址,而指针变量就是存储地址的变量。
- 通过&符号取变量的地址
int a = 20;
printf("%p", &a);// %p打印内存地址
int *p = &a;
与普通变量不同的是,定义指针变量的时候,前面需要加上
*
。使用
*
操作符访问内存编号里对应的内容
#include <stdio.h>
int main(int argc, const char * argv[])
{
int a = 100;
int *p = NULL;// NULL恒等于0
p = &a;// 指针变量是存放地址的变量
//printf("%d\n", *p);// *和&是配套的操作符
//printf("%p\n%p\n%d", &a, p, *p);
printf("%p\n%p\n%d\n%d\n", &a, p, *p, *&a);//0061FF28 0061FF28 100 100
return 0;
}
- 指针的重指向,指针变量的赋值意味着重指向。
#include <stdio.h>
int main(int argc, const char * argv[])
{
int a = 100, b = 200;
int *p = &a;
p = &b;// 指针变量的赋值意味着重指向
printf("%p\n%d\n",p,*p);// 0061FF24 200
return 0;
}
指针变量初始化
- 如果指针变量没有初始化,那么视图使用指针变量的值将会导致未定义的行为。
#include <stdio.h>
int main(int argc, const char * argv[])
{
int *p;
printf("%d\n", *p);
return 0;
}
给指针变量赋值尤其危险
#include <stdio.h>
int main(int argc, const char * argv[])
{
int *p;
*p = 1;//如赋值改变的内存属于该程序则导致不规律的行为,若改变的内存属于操作系统则很可能导致系统崩溃。
printf("%d\n", *p);
return 0;
}
指针的类型
- 因为内存地址只跟操作系统有关,所以指针变量所占内存大小取决于操作系统位数。即32位系统指针占4个字节,64位操作系统指针占8个字节。
- 指针可分为
int *pi
、double *pd
、char *pc
#include <stdio.h>
int main(int argc, const char * argv[])
{
int *pi = NULL;
double *pd = NULL;
char *pc = NULL;
printf("%d %d %d", sizeof(int *), sizeof(double *), sizeof(char *));//4 4 4
return 0;
}
注意事项
- 指针变量定义
int *p=NULL;
此时的*只起到修饰作用,告诉编译器,p是个指针。 -
p=&a; *p=20
//此时的*是取内容操作符,将内存地址为p的存储区域中存入数据20。
指针赋值
C语言允许使用赋值运算符进行指针的赋值,前提是两个指针具有相同的数据类型。
#include <stdio.h>
int main(int argc, const char * argv[])
{
int i, *p, *q;
p = &i;
q = p;// q和p存储都是i的地址
printf("%d %d %d\n", i, *p, *q);// 0 0 0
*p = 1;// 改变i的值为1
printf("%d %d %d\n", i, *p, *q);// 1 1 1
*q = 2;// 改变i的值为2
printf("%d %d %d\n", i, *p, *q);// 2 2 2
return 0;
}
#include <stdio.h>
int main(int argc, const char * argv[])
{
int i, j, *p, *q;
p = &i;//p指向i
q = &j;//q指向j
i = 1;
*q = *p;//将p指向的值"复制"给q指向的值
printf("%d %d %d %d\n", i, j, *p, *q);// 1 1 1 1
return 0;
}
指针作为参数
- 指针到底对什么有益呢?
- 定义个交换两个整型变量的值的函数
#include <stdio.h>
void swap(int i, int j);
int main(int argc, const char * argv[])
{
int i=1, j=2;
swap(i,j);// 函数传参的过程,是把实际参数的值拷贝到形参中。
printf("%d %d\n", i, j);// 1 2
return 0;
}
void swap(int i, int j)
{
int tmp = i;
j = i;
tmp = j;
}
#include <stdio.h>
void swap(int *i, int *j);
int main(int argc, const char * argv[])
{
int i=1, j=2;
swap(&i,&j);
printf("%d %d\n", i, j);// 2 1
return 0;
}
void swap(int *i, int *j)
{
int tmp = *i;//将i变量中存储的地址中的内容赋值给tmp
*i = *j;//将j变量存储地址上的值存储到i变量存储地址的值
*j = tmp;
}
栈
- 由编译器自动分配和释放
- 在函数体中定义的变量通常在栈内存空间上
- 栈中的变量是先进后出
- 栈中的变量一般出了函数就会被释放掉
#include <stdio.h>
int *fn(void);
int main(int argc, const char * argv[])
{
int *pi = fn();
printf("%d\n", *pi);// 1
printf("%d\n", *pi);// 1521606922
return 0;
}
int *fn(void)
{
int num = 1;
int *p = #
return p;
}
静态区
- 全局变量和静态变量的存储是放在一块的,存储在静态区。
- 静态区中的变量在程序结束后立即被释放
#include <stdio.h>
int *fn(void);
int main(int argc, const char * argv[])
{
int *pi = fn();
printf("%d\n", *pi);// 1
printf("%d\n", *pi);// 1
return 0;
}
int *fn(void)
{
static int num = 1; // 静态变量
int *p = #
return p;
}
小结:
- 不仅可为函数传递指针还可编写返回指针的函数
- 函数可以返回指向外部变量或指向声明为static的局部变量的指针
- 永远不要返回指向自动局部变量的指针,因为函数结束后局部变量就不存在了,所以指向局部变量的指针是无效的。
结构体指针
- 指向结构体变量的指针称为结构体指针
#include <stdio.h>
// 定义结构体
typedef struct {
char name[20];
int age;
char sex;
} Student;
int main(int argc, const char * argv[])
{
Student stu = {0};
Student *p = &stu;// 定义结构体指针
(*p).age = 10;//使用结构体指针访问成员
printf("%d\n", p->age);// 10
p->age = 20;
printf("%d\n", p->age);// 20
return 0;
}
结构体数组与指针的关系
#include <stdio.h>
// 定义结构体
typedef struct {
char name[20];
int age;
char sex;
} Student;
int main(int argc, const char * argv[])
{
Student stu[5] = {0};//定义结构体数组
Student *p = stu;// 定义结构体指针
p[0].age = 10;//使用结构体指针访问成员
p[0].sex = 'm';
printf("%d %c\n", p[0].age, p[0].sex);// 10 m
return 0;
}
结构体内包含指针
#include <stdio.h>
#include <string.h>
// 定义结构体
typedef struct {
char nickname[20];
char *username;
} Student;
int main(int argc, const char * argv[])
{
Student stu = {0};//定义结构体数组
strcpy(stu.nickname, "alice");//数组名是一个常量指针,不可以改变其值。
//warning: ISO C++ forbids converting a string constant to 'char*'
stu.username = "bob";//字符指针是一个指针变量可重新赋值
printf("%s %s\n", stu.nickname, stu.username);// alice bob
return 0;
}
数组作为参数
- 取一个数组中的最大元素
- 有一个整型数组,将数组元素大于27的清零。
- 数组作为参数,形参仅代表数组首元素地址,需传入元素个数。