变量
- 局部变量(在函数内部定义的非静态变量)不会自动初始化为默认值,它们的初始值是未定义的(包含垃圾值)。
- 全局变量在没有显式初始化时的默认值:
- 整型变量(int、short、long等):默认值为0。
- 浮点型变量(float、double等):默认值为0.0。
- 字符型变量(char):默认值为'\0',即空字符。
- 指针变量:默认值为NULL,表示指针不指向任何有效的内存地址。
- 数组、结构体、联合等复合类型的变量:它们的元素或成员将按照相应的规则进行默认初始化,这可能包括对元素递归应用默认规则。
- 每一个变量都有一个内存位置,每一个内存位置都定义了可使用 & 运算符访问的地址,它表示了在内存中的一个地址。
数组
概念
- 所有的数组都是由连续的内存位置组成。
- 最低的地址对应第一个元素,最高的地址对应最后一个元素。
- 数组中的特定元素可以通过索引访问,第一个索引值为 0。
- 数组名本身是一个常量指针,意味着它的值是不能被改变的,一旦确定,就不能再指向其他地方。
使用指针操作数组
int myArray[5] = {10, 20, 30, 40, 50};
int *ptr = &myArray[0]; // 等同 int *ptr = myArray;
- 虽然数组名表示数组的地址,但在大多数情况下,数组名会自动转换为指向数组首元素的指针。
- myArray是一个 int (*)[5] 类型的指针,因为它指向一个包含5个整数的数组。
- ptr是一个 int * 类型的指针,因为它指向一个整数。
int myArray[5] = {10, 20, 30, 40, 50};
int *ptr = &myArray; //错误写法,对于数组,不能这样写
int *ptr = myArray; //正确写法
int (*)[5] prt = &myArray; //不能这样写
int x = 1;
int *xx = x; //错误写法
int *xx = &x; //正确写法
- 通过上面写法,可以发现,用变量指针指向数组时,不能像指向普通变量一样直接&
指针
概念
在C语言中,指针本身也是一个变量,它存储了另一个变量的内存地址。
内存
内存单元和内存
内存单元
内存单元是计算机内存中最小的存储单位,通常称为“字节”(byte)。每个内存单元都有一个唯一的地址,可以存储一定量的数据。在大多数现代计算机系统中,一个内存单元通常是8位,即1字节。
内存
内存(Memory)通常指的是整个存储系统,它由大量的内存单元组成。这些内存单元按顺序排列,形成了一个连续的地址空间。在程序运行时,内存用于存储指令、数据和程序状态。
对于int类型,这通常意味着分配了4个字节(32位系统)或8个字节(64位系统)的内存空间,也可以解释为编译器在内存中为这个int型的变量预留了4个内存单元(32位系统),多少个内存单元,取决于数据类型的大小。
内存地址
在计算机内存中,每个内存地址都对应着一块可以存储数据的空间,我们通常称之为内存单元或者字节。
内存地址对应的值取决于内存单元中存储的数据。
变量名就是内存地址的别名,编译器会自动将其转换为对应的内存地址。
例如,如果一个内存单元被用来存储一个整数(如int类型),那么这个内存单元中就会有一个整数值。
如果内存单元中是存储指针值(即指针变量),内存单元中就会存储一个地址值,这个地址值指向其他数据的内存位置,并通过内存地址找到对应的内存单元中的值。
#include <stdio.h>
int main ()
{
int var_runoob = 10; //变量var_runoob对应着一个内存地址,内存地址对应着内存单元,内存单元存储着值10。
int *p = &var_runoob; //指针变量p对应着一个内存地址,内存地址对应着内存单元,内存单元存储着变量var_runoob的内存地址。
}
基本例子
#include <stdio.h>
int main ()
{
int var_runoob = 10; //定义局部变量var_runoob并初始化值为10
printf("var_runoob 变量的地址: %p\n", &var_runoob); //访问var_runoob的内存地址
int *p; //定义指针变量
p = &var_runoob; //指向局部变量var_runoob的内存地址(存储var_runoob的内存地址)
printf("var_runoob 变量的地址: %p\n", p); //使用指针变量访问值,打印结果:var_runoob 变量的地址: 0x7ffeeaae08d8
printf("指针变量p指向的值: %p\n", p); //使用解引用操作符*,来获取指针变量指向的内存地址中存储的值,打印结果:指针变量p指向的值: 10
return 0;
}
- 可以使用&运算符对变量的内存地址进行访问
- 在使用指针变量时,前面带*号代表访问内存地址,不带则表示访问内存地址对应的值
NULL指针
#include <stdio.h>
int main ()
{
int *ptr = NULL;
printf("ptr 的地址是 %p\n", ptr ); //打印结果:ptr 的地址是 0x0
return 0;
}
- 在指针变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个NULL值,代表该指针不指向任何东西。
- 大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。
- 判断是否时NULL指针,可以使用if(!ptr),如果为true,代表是NULL指针。
函数指针
#include <stdio.h>
int max(int x, int y)
{
return x > y ? x : y;
}
int main(void)
{
int (* p)(int, int) = &max; //定义指针变量p,指向函数max的内存地址,可以不写&也行
printf("请输入两个数字:");
int a, b, d;
scanf("%d %d", & a, & b);
d = p(a, b); //使用指针调用max函数,等价:d = max(a, b)
printf("最大的数字是: %d\n", d); //打印结果:最大的数字是: XXX
return 0;
}
- 函数指针是指向函数的指针变量。
- 函数指针可以像一般函数一样,用于调用函数、传递参数。
函数作为参数
#include <stdlib.h>
#include <stdio.h>
//接受参数:变量指针,数组长度,函数指针
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
size_t i;
for (i=0; i<arraySize; i++)
array[i] = getNextValue(); //由于array指针指向myarray,所以赋值后,在main中打印myarray的值也会生效
}
//回调函数,获取随机值
int getNextRandomValue(void)
{
return rand();
}
int main(void)
{
int xx = 1;
int myarray[10];
populate_array(myarray, 10, &getNextRandomValue); //可以不带&
int i;
for(i = 0; i < 10; i++) {
printf("%d ", myarray[i]);
}
printf("\n");
return 0;
}
- c语言支持将函数直接作为参数传入另外一个函数,只是在声明函数时,要以函数指针的格式进行声明,如上面的int (*getNextValue)(void),那么则传入&getNextRandomValue
- java并不支持直接函数作为参数传入另外一个函数,如要实现回调功能,则是先创建interface接口,然后在里面创建回调方法,然后将该接口作为传递给某个类的设备回调方法,类的其他方法调用该接口类的回调方法进行回调。
指针的作用
- 直接操作内存:指针允许程序直接访问和操作内存。这对于需要高性能和精细控制内存使用的程序(如操作系统、设备驱动程序、嵌入式系统等)来说至关重要。
- 动态内存分配:指针使得程序能够在运行时动态地分配和释放内存。这在处理大小不固定数据集或需要创建复杂数据结构(如链表、树、图等)时非常有用。
- 数组操作:指针可以用来高效地处理数组。在C语言中,数组名本身就是一个指向数组首元素的指针,因此可以通过指针算术来遍历数组元素,而无需显式使用索引。
- 函数参数传递:指针允许函数通过引用传递参数,这意味着函数可以修改参数的值,而不仅仅是参数的副本。这对于修改大型数据结构或避免不必要的数据复制非常重要。
- 数据结构实现:许多高级数据结构(如链表、树、哈希表等)依赖于指针来实现。指针使得数据结构能够灵活地连接和动态地调整大小。
- 指针运算:指针可以进行算术运算,如增加或减少指向的地址,这在处理数组和其他数据块时非常有用。
- 多态性:在C语言中,指针可以用来实现有限形式的多态性,即可以通过指向基类型的指针来调用派生类型的函数,这为面向对象编程提供了基础。
- 低级硬件操作:指针用于与硬件接口进行交互,例如,当需要读取或写入特定内存地址或硬件寄存器时。
总之,指针是C语言的核心特性之一,它们为程序员提供了一种强大而灵活的方式来处理内存、数据结构和函数参数。正确使用指针可以显著提高程序的效率和控制能力,但同时也增加了程序的复杂性。因此,理解和掌握指针是成为高效C程序员的关键。
直接操作内存
int var = 10;
int *ptr = &var;
*ptr = 20; // 直接通过指针修改内存中的值
动态内存分配
int *dynamicArray = malloc(10 * sizeof(int)); // 分配一个整数数组的空间
if (dynamicArray != NULL) {
for (int i = 0; i < 10; i++) {
dynamicArray[i] = i;
}
}
free(dynamicArray); // 释放内存
数组操作
int array[] = {1, 2, 3, 4, 5};
int *ptr = array; // 数组名作为指向首元素的指针
for (int i = 0; i < 5; i++) {
printf("%d ", *(ptr + i)); // 通过指针访问数组元素
}
printf("\n");
函数参数传递
void addOne(int *ptr) {
(*ptr)++; // 修改指针指向的值
}
int main() {
int value = 5;
addOne(&value); // 传递地址
printf("Value is now %d\n", value); // 输出 6
return 0;
}
数据结构实现
struct Node {
int data;
struct Node *next;
};
struct Node *createNode(int data) {
struct Node *newNode = malloc(sizeof(struct Node));
if (newNode != NULL) {
newNode->data = data;
newNode->next = NULL;
}
return newNode;
}
指针运算
int array[] = {1, 2, 3, 4, 5};
int *ptr = array;
ptr += 3; // 移动指针到数组的第四个元素
printf("Fourth element is %d\n", *ptr); // 输出 4
多态性
struct Animal {
void (*speak)(struct Animal *);
};
void dogSpeak(struct Animal *animal) {
printf("Woof!\n");
}
void catSpeak(struct Animal *animal) {
printf("Meow!\n");
}
int main() {
struct Animal dog = {dogSpeak};
struct Animal cat = {catSpeak};
dog.speak(&dog); // 输出 "Woof!"
cat.speak(&cat); // 输出 "Meow!"
return 0;
}
低级硬件操作
// 假设有一个硬件寄存器位于地址 0x12345678
volatile unsigned char *hardwareRegister = (volatile unsigned char *)0x12345678;
*hardwareRegister = 0xAA; // 写入寄存器
unsigned char value = *hardwareRegister; // 读取寄存器
通过指针修改变量值和直接修改变量值,有什么区别?
#include <stdio.h>
void changeValueDirectly(int var) {
var = 20; // 直接修改变量值
}
void changeValueViaPointer(int *ptr) {
*ptr = 20; // 通过指针修改变量值
}
int main() {
int a = 10;
printf("Before change: %d\n", a);
changeValueDirectly(a); // 直接修改变量值
printf("After change: %d\n", a); // 输出 10
printf("Before change via pointer: %d\n", a);
changeValueViaPointer(&a); // 通过指针修改变量值
printf("After change via pointer: %d\n", a); // 输出 20
return 0;
}
在这个例子中,changeValueDirectly 函数直接修改变了变量 a 的值,但是由于它是通过值传递的方式调用,所以对 a 的修改只影响了函数内的副本,而没有影响原始的 a 变量。相反,changeValueViaPointer 函数通过指针修改变了 a 的值,因为它是通过引用传递的方式调用,所以对 a 的修改是永久性的。