从零开始的c语言:初步理解指针—从底层到入门(上)指针概念及语法,指针运算, 传地址和传值

从零开始的c语言---初步理解指针(概念详解)

前言:

距离上次更新博客已经过去了一个月,已经学了新知识,但是没有及时完成博客,这次我详细来讲述c语言的大关---------指针。

1. 内存和地址

1.1 内存

我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是8GB/16GB/32GB等,那这些内存空间如何⾼效的管理呢?

其实也是把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。

计算机中常⻅的单位: ⼀个⽐特位可以存储⼀个2进制的位1或者0

中,每个内存单元,相当于⼀个学⽣宿舍,⼀个字节空间⾥⾯能放8个⽐特位,就好⽐同学们住的⼋⼈间,每个⼈是⼀个⽐特位。

1.2 地址

c 复制代码
#include <stdio.h>
int main() {
    int a = 10; // 变量a,存在某块内存里(比如地址是0x0012FF44)
    int *p = &a; // p是指针变量,存的是a的地址(&是"取地址符")
    
    printf("a的值:%d\n", a);       // 输出:a的值:10
    printf("a的地址:%p\n", &a);   // 输出:a的地址:0x0012FF44(具体地址依环境而定)
    printf("p存的地址:%p\n", p);  // 输出:p存的地址:0x0012FF44
    printf("p指向的值:%d\n", *p); // *是"解引用符",通过地址取对应的值 → 输出:10
    return 0;
}

2.指针语法

2.1 指针变量的定义

格式:类型 *指针变量名

类型:指针指向的变量的类型(比如int *p表示 p 指向的是 int 类型变量);

*:表示这是一个指针变量。

cpp 复制代码
// 不同类型的指针
int *p_int;   // 指向int的指针
char *p_char; // 指向char的指针
float *p_float;// 指向float的指针

2.2. 两个关键操作符

取地址符 &:获取变量的内存地址,赋值给指针变量;

解引用符 *:通过指针变量存的地址,访问对应的变量的值。

代码示例(修改指针指向的值):

c 复制代码
int a = 10;
int *p = &a;
*p = 20; // 通过指针修改a的值
printf("a = %d\n", a); // 输出:a = 20

指针的 "大小"
指针变量的大小,只和操作系统的 "地址位数" 有关(和指向的类型无关):
32 位系统:地址是 32 位 → 指针大小是 4 字节;
64 位系统:地址是 64 位 → 指针大小是 8 字节。

c 复制代码
#include <stdio.h>
int main() {
    int *p_int;
    char *p_char;
    printf("int指针大小:%zu字节\n", sizeof(p_int)); // 64位系统输出8
    printf("char指针大小:%zu字节\n", sizeof(p_char));// 64位系统输出8
    return 0;
}

2.3野指针:

1.指针未初始化:指向随机地址(可能导致程序崩溃)

c 复制代码
int *p; // 未初始化的野指针
*p = 10; 

解决:指针初始化时设为NULL(空指针),或直接指向有效变量。

2.指针类型不匹配:比如用char*指针指向int变量(可能导致数据截断)

c 复制代码
int a = 1000;
char *p = &a;  

2.4指针的基本运算

废话不多说,代码如下:

c 复制代码
#include <stdio.h>
int main() {
    int a = 10;
    int* p = &a;
    // 打印指针地址(十六进制)和指针+1后的地址
    printf("p的地址:%p\n", p);       // 例:0x7ffeefbff5c4
    printf("p+1的地址:%p\n", p+1);   // 例:0x7ffeefbff5c8(偏移4字节)
    
    char ch = 'a';
    char* pc = &ch;
    printf("pc的地址:%p\n", pc);     // 例:0x7ffeefbff5c3
    printf("pc+1的地址:%p\n", pc+1); // 例:0x7ffeefbff5c4(偏移1字节)
    return 0;
}

int类型的指针+1------------访问4个字节

char类型的指针+1------------访问1个字节

  1. 指针 - 指针:计算地址间的元素个数,只针对连续存放的数组,否则没有意义。

2.5const修饰指针

之前我们学过了const,但是为什么要在学一遍呢,const原本是为了变量不被修改所以才设置的,请看一下代码

c 复制代码
int main()
{
	const int i = 0;
	printf("i=%d\n", i);  //const修饰整型变量
	int* p = &i;
	*p = 9;
	printf("i=%d\n",*p);
	  
	return 0;
}

首先我们用const修饰了整型变量,但事实上i的值还是改变了,当我们用指针访问时,i仍旧会改变。

const指针的具体用法

const修饰的是它右边最近的内容------ 这是区分所有 const 指针的核心口诀。

指针指向的内容不能被修改(const 修饰指针指向的变量)

语法:const 类型* 指针名 或 类型 const* 指针名(两种写法等价)

规则:指针变量本身可以改(指向其他地址),但通过指针不能修改指向的内容。

c 复制代码
#include <stdio.h>
int main() {
    int a = 10, b = 20;
    const int* p = &a; // 或 int const* p = &a
    
    // *p = 30; → 错误:不能通过p修改a的值
    p = &b; // 正确:指针本身可以指向其他地址
    printf("*p = %d\n", *p); // 输出:*p = 20
    return 0;
}
  1. 指针变量本身不能被修改(const 修饰指针变量)
    语法:类型* const 指针名
    规则:指针变量本身不能改(不能指向其他地址),但通过指针可以修改指向的内容。
    代码示例:
c 复制代码
#include <stdio.h>
int main() {
    int a = 10, b = 20;
    int* const p = &a; // 指针p本身被const修饰
    
    *p = 30; // 正确:可以修改a的值
    // p = &b; → 错误:不能修改p的指向
    printf("a = %d\n", a); // 输出:a = 30
    return 0;
}
  1. 指针本身 + 指向的内容都不能被修改(const 同时修饰两者)
    语法:const 类型* const 指针名
    规则:指针既不能改指向,也不能通过指针修改内容。
c 复制代码
#include <stdio.h>
int main() {
    int a = 10, b = 20;
    const int* const p = &a;
    
    // *p = 30; → 错误:不能改内容
    // p = &b; → 错误:不能改指向
    printf("*p = %d\n", *p); // 输出:*p = 10
    return 0;
}

3,传值调用和传址调用

传地址:

c 复制代码
void reverse(int* x, int* y)//传入指针变量
{
	int temp = 0;
	temp = *x;
	*x = *y;
	*y = temp;//指针交换
}

int main()
{
	int a = 20;
	int b = 10;
	printf("交换前的a=%d,b=%d\n", a, b);//20 10
	reverse(&a, &b);//实参地址
	printf("交换后的a=%d,b=%d\n", a, b);//10 20
	return 0;
}

传值调用

c 复制代码
void change(int x) {
    x = 100; // 修改的是x的副本
}
int main() {
    int a = 10;
    change(a);
    printf("a = %d\n", a); // 输出:a = 10(原变量没被修改)
    return 0;
}

如图所示,传值和传地址是不同的,这是为什么呢,如果是传值,改变形参的变量,实参是不会改变的,形参只是实参的临时拷贝,所以不会影响到是实参。

传地址:通过指针的访问,传入实参的地址,通过指针间接访问实参修改了值。

4.易错点和 "坑"

(*p)++:先取解引用后的值,在加加;

*p++:先取 p 指向的值,再将 p 后移 1 位;

*++p:先将 p 后移 1 位,再取新地址的值。

越界访问:指针偏移超出数组 / 内存边界,会访问到非法内存

c 复制代码
#include<stdio.h>
#include<assert.h>
#include<string.h>
int main()
{
	int arr[10] = { 0 };
	int* p = &arr[0];
	int i = 0;
	for (i = 0; i <= 11; i++)
	{
		*(p++)=i;//指针越界访问
	}
	p = NULL;
	 
	assert(p != NULL);//括号里面的是条件

	return 0;
}
c 复制代码
////实现strlen功能
int my_strlen(char* p)
{
	int count = 0;
	assert(p != NULL);
	while (*p)//遍历数组的字符=*p!='\0'
	{
		count++;
		p++;
		 
	}
	return count;
	 
}
 
int main()
{
	char arr[] = "abcdef";
	int len=my_strlen(arr);
	printf("%d", len);
	
	return 0;
 
 }


不积跬步,无以至千里。

相关推荐
Brookty7 小时前
Java并发编程核心的基础知识
java·开发语言·java-ee·多线程·线程安全
hellotutu7 小时前
Java 读取 Excel 文件
java·开发语言·excel
胡萝卜3.07 小时前
构建安全的C++内存管理体系:从RAII到智能指针的完整解决方案
运维·开发语言·c++·人工智能·安全·智能指针·raii
MSTcheng.7 小时前
【C++】如何快速实现一棵支持key或key-value的二叉搜索树?关键技巧一文掌握!
开发语言·c++·算法·二叉搜索树
ByNotD0g7 小时前
Go 泛型 in 1.25
开发语言·后端·golang
非凡ghost7 小时前
FlexiPDF(专业PDF编辑软件)
windows·学习·pdf·软件需求
野生风长7 小时前
从零开始的c语言:指针高级应用(下)(回调函数,qsort函数模拟实现, strlen和sizeof)
java·c语言·开发语言·c++·算法
chao1898447 小时前
C# 实现画板源码
开发语言·c#
习惯就好zz7 小时前
Godot GDExtension 4.5 windows编译记录
windows·godot·cpp·gdextension
lly2024067 小时前
Eclipse 创建 Java 包
开发语言