C语言基础快速入门与指针初探

接下来,C 和 C++ 的学习都将在 Visual Studio 上进行,没使用过的,可以查看教程。

第一个程序:Hello World

先来一个 Hello World:

c 复制代码
#include <stdio.h> // 导入标准输入输出头文件

int main() { // 函数的主入口
	printf("Hello World!");
	return 0; // 程序正常退出
}

头文件(.h、.hpp)中有着函数声明和宏定义,# 开头的都是预处理指令。

例如,#include 的作用是在预处理阶段 将头文件中的声明代码复制粘贴 到指令处。然后在链接阶段 ,链接器会找到声明对应的实现(在源文件中,文件后缀为 .c、.cpp),并拼接到程序中,最终生成可执行程序。

在编译汇编阶段,会生成目标文件,检查调用到的函数是否有对应声明,没有的话会报错,例如:

c 复制代码
#include <stdio.h>

int main() {
	print("Hello World!"); // 标准库中不存在print的函数声明,会报错未定义标识符
	return 0;
}

常用的基本数据类型

C语言中常用的基本数据类型主要有以下几种:

此外,随着标准的演进,还有 long long 类型、无符号类型 unsigned 及布尔类型等。

c 复制代码
#include <stdio.h>

int main() { 
	int i = 100;
	double d = 200.0;
	float f = 150.0f;
	long l = 1000L;
	short s = 20;
	char c = 'c';

	// 占位符打印
	printf("i's value is %d\n", i); // 100
	printf("d's value is %lf\n", d); // 200.000000
	printf("f's value is %f\n", f); // 150.000000
	printf("l's value is %ld\n", l); // 1000
	printf("s's value is %d\n", s); // 20
	printf("c's value is %c\n", c); // c
 
	return 0;
}

不同的数据类型区别在于变量存储时所占用的空间不同,表示的值范围不同,并且存储的方式也有差异。

以 char 为例,它只占 1 字节,值的表示范围只有 -128 到 127。int 占 2 或 4 字节,值的范围是 -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647。

int 的大小不唯一,是因为在不同位数的操作系统上有所差异,可以使用 sizeof 操作符来获取准确大小:

c 复制代码
#include <stdio.h>

int main() {
	int number = 100;
	// sizeof 返回的是数据类型是 size_t,推荐使用 %zu 占位符
	printf("number's size is %zu\n", sizeof(number)); // 获取变量的大小, 4
	printf("short's size is %zu\n", sizeof(short)); // 获取类型的大小, 2
	return 0;
}

如果在很老旧的编译器下不支持 %zu,可以强转为 int 进行打印((int)sizeof(number))。

变量的地址

每个变量在创建时,都会在内存中开辟一块空间,而该空间的位置就是变量的地址。

c 复制代码
#include <stdio.h>

int main() { 
	int num = 200;
	// %p 表示地址占位符
	printf("num's address is %p", &num); // & 是取地址符
	return 0;
}

运行结果可能类似:

复制代码
00000063DB36FC24

可以打个断点进行调试,然后在内存窗口中查找 00000063DB36FC24 地址的值,并且将显示模式改为 4 字节整数及带符号显示,你就能够看到 200 的数据值。

获取地址处存放的值,使用的操作符是 *,也叫解引用操作符或取值符。

c 复制代码
#include <stdio.h>

int main() {
	int a = 5;
	printf("a's value is %d\n", a);
	printf("a's value is %d", *(&a));
	return 0;
}

这两行打印的结果是完全相同的。

初识指针:房间与纸条

接收变量地址的变量就叫做指针(指针变量),你可以把变量看作是一个房间,房间里面有着数据,指针是记录别的房间地址的纸条,本身并没有存放具体的数据值。

例如 int a = 5;,其中 a 就是房间,进入房间就可以看见 5,我们获取值不需要额外操作。

但 int *p = &a; 就不同,p 这个纸条存放着 a 房间的详细地址,我们只有按地址找到对应房间,才能够获取 5。直接读纸条,只能得到房间的地址。

c 复制代码
#include <stdio.h>

int main() {
	int a = 5;
	// 指针,指向了a,拿着a的地址
	int* p = &a;

	// 解引用:顺着纸条找到房间,读取里面的值
	printf("%d", *p); // 5
        // 直接打印纸条的内容:得到的是地址
	printf("%p", p); // 例如 00000063DB36FC24
	return 0;
}

如果你使用 %d 占位符打印指针的话,在 64 位系统上会发生数据截断,得到非真实、准确的值(指针 8 字节,%d 通常是 4 字节,高位数据被丢弃了)。

并且显示的是无意义的负数,内存地址是无符号整数,但 %d 是有符号整数的格式,经过前面的截取后,数据的最高位往往是 1,所以会显示负数,与真实的地址不符。

通过指针修改原变量

我们通过指针修改值的时候,也会修改原变量。

c 复制代码
#include <stdio.h>

int main() {
	int a = 5;
	int* p = &a;

	printf("Before, a is %d\n", a); // 5
	*p = 10;
	printf("After, a is %d", a); // 10
	return 0;
}

为什么?

因为操作 *p 和直接操作 a 是等效的,操作的都是同一块内存空间的数据。

降维打击:为什么我们需要指针?

在 Java 中,你不能在方法内部修改外部变量本身(基本类型),这样做不到:

java 复制代码
public class Demo {
    public static void main(String[] args) {
        int a = 10;
        System.out.println(a); // 10
        change(a);
        System.out.println(a); // 10,外部a的值还是10
    }

    public static void change(int num) { 
        num = 999;
    }

}

为什么?因为 Java 只有值传递,根本就没有暴露底层的指针。

对于基本数据类型,传的是值的副本;对象传的是引用地址的副本,在方法内部都创建了新的局部变量来接收。

而 C 语言可以通过传递地址,实现在函数内部修改外部实际参数,例如:

c 复制代码
#include <stdio.h>

// 传递变量的内存地址
void change(int* p) {
    // 解引用直接操作原内存
    *p = 999;
}

int main() {
    int a = 10;
    printf("%d\n", a); // 10
    change(&a); 
    printf("%d", a); // 999,成功修改
    return 0;
}

再提一嘴,Java 对象看起来能在方法内修改外部数据,是因为它传递的是指针(引用)的副本,并且经过了安全封装,我们只能通过它来修改对象内部的属性。

java 复制代码
// 实参变量
Person person = new Person();
// 形参变量
static void func(Person p){ ... }

原理:外部栈变量,存放着堆中对象的地址;内部形参也是栈上的变量,存着同一份地址。不管改动哪个,都是去修改堆上的同一块内存,不会影响到这两个栈上原始的变量本身,也就是说你不能让外部的 person 变为另一个新对象。

相关推荐
五彩小白2 小时前
自回归和上下文
android
事圆则缓4 小时前
Android 多渠道打包实战:Product Flavors、签名、AAB 与发包校验
android
树下困觉眯6 小时前
View绘制流程学习--获取WindowManager的三种方式
android·系统架构·view design
vilya7 小时前
我怎么给手机 GUI Agent 做双通道感知:无障碍树为主,投屏像素兜底
android·人工智能
李子红了时9 小时前
阿狸舞台APP(安卓端手机EncFS解密+压缩包解压+视频播放)
android·音视频
事圆则缓9 小时前
Android 使用 Jenkins 实现 CI/CD,并用 SonarQube 建立质量门禁
android·ci/cd·jenkins
其实防守也摸鱼9 小时前
DeepSeek Harness 开源贡献手记:从 Issue 到 Merge 的完整旅程
android·数据库·学习·ai·oracle·自动化
恋猫de小郭9 小时前
Android CLI 支持 AI Agent 通过 Device Streaming 调试云真机
android·前端·flutter
素师良码1 天前
第5篇:显示驱动必备调试工具浅析
android
维克兜率天1 天前
【维克】配对交易的季节性:哪些品种适合长拿?
android·开发语言·笔记·python·算法·kotlin·量化