C语言关键字
register
修饰变量存储在寄存器中,提升效率 但不能取地址。
restrict
- 内存访问优化:编译器可以假设指针指向的内存区域不会被其他指针修改,从而优化内存访问。
-
提高性能:通过减少不必要的内存访问和优化指令顺序,
restrict
关键字可以显著提高程序的性能
C语言中的负数 :取绝对值后按位取反 +1
指针
& 取地址 * 取值
数组名是数组第一个元素的地址
char*p;
p = a;
p = &a[0];等价
指针的运算:
当指针指向数组元素的时候,我们可以对指针变量进行加减运算,这样做的意义相当于指向距离指针所在位置向前或向后的第n个元素
数组名只是一个地址 指针是一个左值
在C语言中,左值(Lvalue)和右值(Rvalue)是表达式中的两种不同类型的值,它们在赋值操作中扮演不同的角色。
C语言中的指针是一个非常强大和灵活的特性,它允许程序直接操作内存地址。以下是对C语言指针的详细介绍以及各种使用场景的示例。
一、指针的基本概念
- 指针变量的声明:指针变量用于存储内存地址。声明指针变量时需要使用 * 符号,例如:
int *p; /* p是一个指向整数的指针 */
- 指向一个变量:
- 使用 & 操作符获取变量的地址。
- 例如:
int a = 10; int *p = &a; /* p现在存储了变量a的地址 */
二、指针的基本使用
- 访问指针指向的值:使用 * 操作符解引用指针,获取指针所指向的变量的值。
int a = 10;
int *p = &a;
printf("a的值是:%d\n", *p); /* 输出a的值 */
三、指针和数组
- 指针和数组的关系:数组名本身就是一个指针,指向数组的第一个元素。
int arr[] = {1, 2, 3, 4, 5};
int *p = arr; /* p指向数组的第一个元素 */
printf("arr[0]的值是:%d\n", *p); /* 输出1 */
**遍历数组**:可以通过指针来遍历数组。
int arr[] = {1, 2, 3, 4, 5};
int *p;
for (p = arr; p < arr + 5; p++) {
printf("%d\n", *p);
}
四、指针和字符串
- C语言中的字符串:C语言中的字符串实际上是一个字符数组,以 \0 结尾。
- 字符串操作:可以使用指针来操作字符串。
char str[] = "Hello";
char *p = str;
printf("字符串的内容是:%s\n", p); /* 输出Hello */
五、指针和函数
- 函数参数传递:可以将指针作为函数参数传递,从而实现对变量的间接访问和修改。
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
swap(&x, &y);
printf("x = %d, y = %d\n", x, y); /* 输出x = 20, y = 10 */
return 0;
}
六、指针和动态内存分配
- 动态内存分配:可以使用 malloc、calloc 和 realloc 函数动态分配内存。
int *p = (int *)malloc(5 * sizeof(int)); /* 分配5个整数的空间 */
if (p != NULL) {
for (int i = 0; i < 5; i++)
{
p[i] = i + 1;
}
for (int i = 0; i < 5; i++)
{
printf("%d\n", p[i]);
}
free(p); /* 释放内存 */
}
七、多级指针
- 多级指针:指针可以指向另一个指针。
int a = 10;
int *p = &a;
int **q = &p;
printf("a的值是:%d\n", **q); /* 输出10 */
八、指针和结构体
- 结构体指针:可以使用指针来访问结构体的成员。
struct Person {
char name[50];
int age;
};
int main() {
struct Person *p;
p = (struct Person *)malloc(sizeof(struct Person));
strcpy(p->name, "Alice");
p->age = 25;
printf("Name: %s, Age: %d\n", p->name, p->age);
free(p);
return 0;
}
九、指针和函数指针
- 函数指针:可以将函数地址赋值给指针变量,通过指针调用函数。
int add(int a, int b) {
return a + b;
}
int main() {
int (*func)(int, int) = add;
printf("1 + 2 = %d\n", func(1, 2)); /* 输出3 */
return 0;
}
十、指针和数组多维数组
- 多维数组的指针:可以使用指针来访问多维数组。
int arr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int (*p)[3] = arr; /* p是一个指向包含3个整数的数组的指针 */
printf("%d\n", p[0][1]); /* 输出2 */
十一、指针和NULL
- 空指针:NULL 是一个特殊的指针值,表示指针不指向任何有效的内存地址。
int *p = NULL; /* p是一个空指针 */
if (p == NULL) {
printf("指针p没有指向任何有效的内存地址。\n");
}
十二、指针和常量
- 常量指针:指针本身是常量,不能修改它所指向的地址。
const int *p = &a; /* p是一个指向常量整数的指针 */
*p = 20; /* 错误:不能修改常量 */
- 指针常量:指针指向的地址是常量,不能修改指针所指向的值。
int *const p = &a; /* p是一个常量指针 */
p = &b; /* 错误:不能修改常量指针 */
十三、指针和递归
- 递归中的指针:指针可以用于递归算法中,例如递归遍历链表。
struct Node {
int data;
struct Node *next;
};
void printList(struct Node *head) {
if (head == NULL) {
return;
}
printf("%d\n", head->data);
printList(head->next);
}
十四、指针和内存泄漏
- 内存泄漏:如果动态分配的内存没有被释放,就会导致内存泄漏。
int *p = (int *)malloc(sizeof(int));
p = (int *)malloc(sizeof(int)); /* 前一个分配的内存没有被释放,导致内存泄漏 */
free(p); /* 只释放了最后一次分配的内存 */
十五、指针和内存越界
- 内存越界:访问超出数组或指针范围的内存会导致未定义行为。
int arr[5];
arr[5] = 10; /* 内存越界 */
十六、指针和联合体
- 联合体指针:可以使用指针来访问联合体的成员。
union Data {
int i;
float f;
char str[20];
};
int main() {
union Data *data;
data = (union Data *)malloc(sizeof(union Data));
data->i = 10;
printf("data->i = %d\n", data->i);
free(data);
return 0;
}
十七、指针和类型转换
- 类型转换:可以使用 (type *) 进行指针类型转换。
int a = 10;
void *p = &a; /* p是一个void指针 */
int *q = (int *)p; /* 类型转换 */
printf("a的值是:%d\n", *q);
十八、指针和数组指针
- 数组指针:指针可以指向整个数组。
int arr[5];
int (*p)[5] = &arr; /* p是一个指向包含5个整数的数组的指针 */
printf("%d\n", (*p)[2]); /* 输出arr[2] */
十九、指针和位运算
- 位运算和指针:指针不直接涉及位运算,但可以通过指针和整数之间的转换来操作位。
int a = 0x1234;
unsigned char *p = (unsigned char *)&a;
printf("0x%02X\n", *p); /* 输出低位 */
二十、指针和模块化编程
- 模块化编程中的指针:指针可以用于实现模块化编程,例如通过指针传递函数回调。
void process(int (*func)(int)) {
printf("Result: %d\n", func(5));
}
int square(int x) {
return x * x;
}
int main() {
process(square); /* 输出Result: 25 */
return 0;
}
二十一、指针和互斥操作
- 多线程中的指针:在多线程环境中,指针需要小心使用,以避免竞争条件和数据不一致。
#include <pthread.h>
int count = 0;
pthread_mutex_t mutex;
void *increment(void *arg) {
pthread_mutex_lock(&mutex);
count++;
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t threads[10];
pthread_mutex_init(&mutex, NULL);
for (int i = 0; i < 10; i++) {
pthread_create(&threads[i], NULL, increment, NULL);
}
for (int i = 0; i < 10; i++) {
pthread_join(threads[i], NULL);
}
printf("Count: %d\n", count); /* 输出正确值 */
pthread_mutex_destroy(&mutex);
return 0;
}
总结
C语言中的指针是一个非常强大和灵活的工具,它允许直接操作内存,这在许多场景中都非常有用。然而,指针的使用也需要非常小心,以避免内存泄漏、内存越界和未定义行为等问题。通过本文介绍的这些示例和使用方法,您可以更好地理解和掌握C语言中的指针。
1、常量指针
常量指针的概念是:指针所指空间的值不能发生改变,不能通过指针解引用修改指针所指向空间的值,但是指针的指向是可以改的
2、指针常量
指针常量的概念是:指针本身就是一个常量,即指针的指向不能发生改变,但是指针所指向空间的值是可以发生改变的,可以通过解引用改变指针所指向空间的值
具体怎么更好的理解呢?
大家先看常量指针这个名字,以指针结尾,所以是具有指针的性质的,因此可以改变指向,又因为是常量指针,所以指向的值是常量的,即是不能改变的
再看指针常量这个名字,以常量结尾,应该具有常量的一些性质,所以自然不能改变指针的指向,只能修改指针所指向的值啦
那么const究竟加到代码的哪个位置才叫常量指针,加到哪个位置才叫指针常量呢?
说一个小窍门可以很快的帮助大家记住:
const如果在的左边,那就是常量指针
const如果在的右边,那就是指针常量
至于为什么要这样记呢,可以思考一下,在指针中有解引用的作用,那么const在左边,可以理解为const修饰*,即不能修改指针所指向空间的值,但是指针的指向确是没有限制的
那么const在*右边,修饰的就是指针变量啦,即指针变量不能改变它的指向,但是指针所指向空间的值不受限制
常量指针:const int *pa = &a;、int const *pa = &a;
指针常量:int *const pa = &a;
左值(Lvalue)
左值是指那些可以出现在赋值操作符左边的表达式,通常表示一个具有持久存储的对象。左值的主要特征包括:
- 可寻址:左值可以获取其内存地址。
- 可修改:左值可以被重新赋值。
- 示例:变量名、数组元素、解除引用的指针等。
右值(Rvalue)
右值则是指那些没有持久存储的对象,通常表示临时计算的结果。右值的主要特征包括:
- 不可寻址:右值不能获取其内存地址。
- 临时性:右值通常在表达式求值后被销毁。
-
示例:字面量(如整数常量42)、临时对象(如
a + b
的结果)。
左值和右值的区别
- 存储位置:左值有持久存储,而右值没有持久存储。
- 赋值操作:左值可以出现在赋值操作的左边,而右值只能出现在右边。
-
生命周期:左值的生命周期较长,可以在表达式结束后依然存在;而右值的生命周期较短,通常在表达式结束后被销毁。
指针和数组的区别
数据名只是一个地址,而指针是一个左值
sizeof(数组)
返回数组总大小,sizeof(指针)
返回指针大小(如 4/8 字节)。
动态内存分配函数有哪些?
-
malloc(size)
:分配未初始化的内存。 -
calloc(num, size)
:分配并初始化为 0 的内存。 -
realloc(ptr, new_size)
:调整已分配内存的大小。
反转链表:
struct Node {
int data;
struct Node* next;
};
struct Node* reverse_list(struct Node* head) {
struct Node *prev = NULL, *current = head, *next = NULL;
while (current != NULL) {
next = current->next;
current->next = prev;
prev = current;
current = next;
}
return prev;
}