1、指针基础知识
1.1 指针和指针变量的区别
1.2 指针变量的定义和使用
指针变量是一个无符号的整型数。
下面是一个例子:
#include <stdio.h>
int main()
{
int a = 0;
char b = 100;
printf("%p, %p\n", &a, &b); //打印a, b的地址
//int *代表是一种数据类型,int*指针类型,p才是变量名
//定义了一个指针类型的变量,可以指向一个int类型变量的地址
int *p;
p = &a;//将a的地址赋值给变量p,p也是一个变量,值是一个内存地址编号
printf("%d\n", *p);//p指向了a的地址,*p就是a的值
char *p1 = &b;
printf("%c\n", *p1);//*p1指向了b的地址,*p1就是b的值
return 0;
}
注意:&可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的。
1.3 通过指针间接修改变量的值
int main() {
int a = 20;
int* p; //指针类型的变量,存储变量的地址
p = &a; //把变量a对应的地址赋值给指针变量
//下面两个结果相同
printf("%p\n", &a);
printf("%p\n", p);
*p = 100; //改变指针变量(地址)对应的值
//下面打印内容相同,都是改变后的值=100
printf("%d\n", *p); //就是将指针变量值(也就是地址)对应的内容打印出来
printf("%d\n", a);
return 0;
}
1.4 指针大小
note1:
- &操作符是取变量的地址,是升维的;
- *操作符是将地址对应的内容取出来,是降维的。
note2:
这里还要注意,前面说过指针变量是一个无符号的整型数,那么为什么还会有不同类型的指针变量呢?
看个例子:
int main() {
char a = 100; //ASCII码值100=d
int* p;
p = &a;
printf("%d\n", *p); //打印ASCII码值,出错了
return 0;
}
上面的程序运行发现,打印的并不是ASCII码值100,但是当把指针变量定义成char类型后,就正确了。
答:
单独看指针变量确实没什么必要,但是当用指针变量来取对应的值时,这个时候就要对应,不然会出错的。
说了半天,就是说,定义指针变量的时候,类型要和变量的类型对应上。
1.5 野指针和空指针
1.5.1 野指针
指针变量也是变量,是变量就可以任意赋值,不要越界即可(32位为4字节,64位为8字节),但是,任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域)。所以,野指针不会直接引发错误,操作野指针指向的内存区域才会出问题。
例子:
#include <stdio.h>
int main() {
int a = 100;
int* p;
p = a; //把a的值赋值给指针变量p,p为野指针, ok,不会有问题,但没有意义
p = 0x12345678; //给指针变量p赋值,p为野指针, ok,不会有问题,但没有意义
*p = 1000; //操作野指针指向未知区域,内存出问题,err
return 0;
}
1.5.2 空指针
野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量(空闲可用),C语言中,可以把NULL赋值给此指针,这样就标志此指针为空指针,没有任何指针。
例子:
int main() {
//空指针是指内存地址编号为0的空间
int* p = NULL;
*p = 100; //操作空指针对应的空间一定会报错
printf("%d\n", *p);
//空指针可以用来条件判断
return 0;
}
1.6 万能指针
void *指针可以指向任意变量的内存空间。注意:通过万能指针是不是直接修改变量的值的,必须要确定指针的类型才能操作。
例子:
#include <stdio.h>
int main() {
int a = 10;
void* p;
//万能指针可以接收任意类型变量的内存地址
p = &a;
//在通过万能指针修改变量的值时,需要找到变量对应的指针类型
//*p = 100; //err
printf("%d\n", sizeof(p)); //万能指针占的字节大小:4
return 0;
}
1.7 const修饰的指针变量
前面说过,const表示的是常量,修饰指针也是一样的,这个时候就是指针常量,这个指针常量本身的值是可以修改的,但是指向的值是不能变的。
例子:
#include <stdio.h>
int main() {
int a = 10;
int b = 20;
const int* p = &a; //指针常量
p = &b; //本身的值可以修改
//*p = b; //这个就会报错,因为这个p是指针常量,指向的值是不能修改的
printf("%d\n", *p); // 20
return 0;
}
2、指针和数组
2.1 数组名
数组名字是数组的首元素地址,是一个常量。
例子:
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
printf("a = %p\n", a); //a本身表示第一个元素的地址
printf("&a[0] = %p\n", &a[0]);
//a = 10; //err, 数组名只是常量,不能修改
2.2 指针操作数组元素
由于数组名就是数组首元素的地址,那么把这个地址给一个指针变量,那操作这个指针变量也同样能改变数组。下面来看:
例子:
#include <stdio.h>
int main()
{
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int i = 0;
int n = sizeof(a) / sizeof(a[0]);
for (i = 0; i < n; i++)
{
//printf("%d, ", a[i]);
printf("%d, ", *(a + i)); //与上面等价,这个对地址加1,其实加类型的占用地址,这里是4
}
printf("\n");
int* p = a; //定义一个指针变量保存a的地址
for (i = 0; i < n; i++)
{
p[i] = 2 * i; //操作指针,同样改变数组
}
for (i = 0; i < n; i++) //验证,确实改变了
{
//printf("%d, ", p[i]); //方式一
printf("%d, ", *(p + i)); //方式二
}
printf("\n");
return 0;
}
注意:
- 指针计算不是简单的整数相加
- 如果是一个int *,+1的结果是增加一个int的大小
- 如果是一个char *,+1的结果是增加一个char大小
2.3 指针数组
指针数组,它是数组,数组的每个元素都是指针类型。
例子:
#include <stdio.h>
int main()
{
//指针数组
int* p[3]; //指针数组,里面的每一个元素都是指针类型
int a = 1;
int b = 2;
int c = 3;
int i = 0;
p[0] = &a; //将变量的地址给指针数组的每一个元素
p[1] = &b;
p[2] = &c;
for (i = 0; i < sizeof(p) / sizeof(p[0]); i++)
{
printf("%d, ", *(p[i]));
}
printf("\n");
return 0;
}
2.4 多级指针
- C语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针。
- 二级指针就是指向一个一级指针变量地址的指针。
#include <stdio.h>
int main() {
int a = 10;
int* p = &a; //一级指针
*p = 100; //*p就是a
int** q = &p;
//*q就是p
//**q就是a
int*** t = &q;
//*t就是q
//**t就是p
//***t就是a
return 0;
}
3、指针和函数
3.1 函数形参改变实参的值
若使用指针作为函数的形参,那么会改变实参的值。
例子:
#include <stdio.h>
void swap1(int x, int y) // 形参的改变不会影响实参
{
int tmp;
tmp = x;
x = y;
y = tmp;
//printf("x = %d, y = %d\n", x, y);
}
void swap2(int* x, int* y) //函数形参改变会影响实参的值
{
int tmp;
tmp = *x;
*x = *y;
*y = tmp;
}
int main()
{
int a = 3;
int b = 5;
swap1(a, b); //值传递
printf("a = %d, b = %d\n", a, b);
a = 3;
b = 5;
swap2(&a, &b); //地址传递
printf("a2 = %d, b2 = %d\n", a, b); //5 3
return 0;
}
3.2 数组名做函数参数
数组名做函数参数,函数的形参会退化为指针:
#include <stdio.h>
void printArrary(int* a, int n) //因为数组名做函数参数,会退化为指针,所以后面的n必须要传
{
int i = 0;
for (i = 0; i < n; i++)
{
printf("%d, ", a[i]);
}
printf("\n");
}
int main()
{
int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int n = sizeof(a) / sizeof(a[0]); //数组的长度
//数组名做函数参数
printArrary(a, n);
return 0;
}
3.3 指针做为函数的返回值
例子1:在字符串中查找第一次出现的字符
代码:
#include <stdio.h>
//在字符串中查找第一次出现的字符
char* my_strchar01(char* str, char ch) {//方法一:使用下标查找
int i = 0;
while (str[i]) {
if (str[i] == ch) {
return &str[i];//返回查找到字符的指针
}
i++;
}
return NULL;//如果没找到,返回NULL,即空指针
}
char* my_strchar02(char* str, char ch) {//方法二:使用数组名作为指针查找
while (*str) {
if (*str == ch) {
return str;//返回当前地址
}
str++;
}
return NULL;
}
int main() {
char str[] = "hello world";
//char*p = my_strchar01(str, 'l');
char*p = my_strchar02(str, 'l');
if (p != NULL) {
printf("查找成功,值为:%s", p); //查找成功,值为:llo world
}
else {
printf("查找失败!");
}
return 0;
}
例子2:从字符串中查找子串。
思想:定义三个指针,如下图所示,最上面的一个指针是遍历源字符串的指针,中间的一个是记录相同字符串的首地址,最下面一个是遍历子串的指针。
然后让上下两个指针遍历源字符串和子串,若不相等,则把中间指针的下一个位置设置为源字符串的指针,依次类推。
代码:
#include <stdio.h>
char* my_strstr(char* str1, char* str2) {
char* fstr1 = str1;//遍历源字符串的指针
char* rstr1 = str1;//记录相同字符串的首地址
char* tstr2 = str2;//遍历子串的指针
while (*str1) {
rstr1 = fstr1;
while (*fstr1 == *tstr2 && *fstr1!='\0') {//对比字符串和子串中的字符
fstr1++;
tstr2++;
}
if (*tstr2=='\0') {
return rstr1;//若把子串遍历完了,说明子串在原串中,返回记录相同字符串指针,也就是第一个
}
//回滚
tstr2 = str2;//不相等,子串指针回到首地址
fstr1 = rstr1 + 1;//遍历源字符串的指针向后走一个
}
return NULL;//空指针
}
int main()
{
char str1[] = "helle world";
char str2[] = "llo";
char* p = my_strstr(str1, str2);
printf("%s\n", p);//llo world
return 0;
}
4、指针和字符串
4.1字符指针
代码:
#include <stdio.h>
int main()
{
char str[] = "hello world";
char* p = str;
*p = 'm';
p++;
*p = 'i';
printf("%s\n", str);//millo world,说明能修改字符数组的值
p = "mike jiang";
//*p = "dddd"; //错误,说明不能修改指针字符串的值
printf("%s\n", p);//mike jiang
return 0;
}
结论是:通过指针修改字符串的值,只能定义一个字符串数组,然后让指针指向它,就可以通过指针修改了。如果直接定义一个指针类型的字符串,就不能修改里面的值。
4.2 字符指针做函数参数
c语言没有字符串类型,所以要定义一个字符串,其实就是定义了一个字符类型的数组。其实字符指针做函数参数,就和前面说的数组一样,退化为指针了。
下面看一个求字符串长度的例子:
正常的思路是:定一个一变量,然后遍历字符串,每次自加这个变量即可。但现在我们使用指针求这个问题。思路是:定义两个指针,一个不动,一个自加,最后字符串的长度就是两个指针的差。
代码:
#include <stdio.h>
char* mystr(char* str1, char* str2) {
return str1;
}
char* my_str_len(char* str) {
char* dest = str;
while (*dest) {
dest++;
}
return dest - str;//返回字符串的长度
}
int main() {
char str1[] = "hello world!";
char str2[] = "hello c!";
int len = my_str_len(str1);
printf("字符串的长度为:%d\n", len);
return 0;
}
4.3 const修饰的指针变量
const我们都知道,简单的来说就是让变量变成常量。看一下const修饰指针变量,下面的两种情况:
const char* p;
char* const p2;
这两个区别是什么???
答:
- 前者:说明指针指向的内存不能改变;
- 后者:说明指针的指向不能改变,指针的值不能修改。
看一个例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
//const修饰一个变量为只读
const int a = 10;
//a = 100; //err
//指针变量, 指针指向的内存, 2个不同概念
char buf[] = "aklgjdlsgjlkds";
//从左往右看,跳过类型,看修饰哪个字符
//如果是*, 说明指针指向的内存不能改变
//如果是指针变量,说明指针的指向不能改变,指针的值不能修改
const char* p = buf;
// 等价于上面 char const *p1 = buf;
//p[1] = '2'; //err
p = "agdlsjaglkdsajgl"; //ok
char* const p2 = buf;
p2[1] = '3';
//p2 = "salkjgldsjaglk"; //err
//p3为只读,指向不能变,指向的内存也不能变
const char* const p3 = buf;
return 0;
}
4.4 指针数组做为main函数的形参
int main(int argc, char *argv[]);
- main函数是操作系统调用的,第一个参数标明argc数组的成员数量,argv数组的每个成员都是char *类型
- argv是命令行参数的字符串数组
- argc代表命令行参数的数量,程序名字本身算一个参数
#include <stdio.h>
//argc: 传参数的个数(包含可执行程序)
//argv:指针数组,指向输入的参数
int main(int argc, char *argv[])
{
//指针数组,它是数组,每个元素都是指针
char *a[] = { "aaaaaaa", "bbbbbbbbbb", "ccccccc" };
int i = 0;
printf("argc = %d\n", argc);
for (i = 0; i < argc; i++)
{
printf("%s\n", argv[i]);
}
return 0;
}
5、总结:
定义 | 说明 |
---|---|
int i | 定义整形变量 |
int *p | 定义一个指向int的指针变量 |
int a[10] | 定义一个有10个元素的数组,每个元素类型为int |
int *p[10] | 定义一个有10个元素的数组,每个元素类型为int* |
int func() | 定义一个函数,返回值为int型 |
int *func() | 定义一个函数,返回值为int *型 |
int **p | 定义一个指向int的指针的指针,二级指针 |