字符串指针
字符串指针不能直接被赋值的原因是,字符串常量存储在只读的内存区域,而字符串指针指向的是字符串常量的地址。当我们将字符串赋值给字符指针时,实际上是将字符串常量的地址赋给了字符指针。由于字符串常量是只读的,所以我们不能通过字符指针(*p)来修改字符串常量的值。这是为了保证字符串常量的不可变性和程序的安全性。
字符串常量存储在程序的静态存储区,也称为常量区。这是一个特殊的内存区域,用于存储不可更改的数据,包括字符串常量。在编译时,编译器会将字符串常量放入这个区域,并为每个字符串分配一个唯一的地址。在程序运行期间,可以通过引用这个地址来访问字符串常量。由于字符串常量是不可修改的,所以它们通常被视为只读数据。
- char str[] = "hello, world";
- str[1] = 'a';
- char *str = "hello, world";
- str[1] = 'a';
的区别:
这两段代码的区别在于字符串的存储方式和修改能力。
第一段代码使用字符数组来存储字符串,即将字符串的每个字符存储在一个连续的内存位置上。在这种情况下,我们可以直接通过索引来修改数组中指定位置的字符,例如将 str[1] 修改为 ‘a’。因此,第一段代码是合法的。
第二段代码使用字符指针来存储字符串,即将字符串的地址存储在指针变量中。在这种情况下,指针指向的内存位置是只读的,也就是说不能直接修改字符串中的字符。因此,第二段代码是不合法的。
简而言之,第一段代码可以修改字符串中的字符,而第二段代码则不能。
在某些情况下,指针指向的内存位置可能被标记为只读。这意味着您无法通过该指针来修改该内存位置的值。有几种情况可能导致内存位置变为只读:
内存位置只读
- 常量指针:如果您声明了一个指向常量的指针,即使用 const 关键字将指针声明为常量,那么您只能通过该指针读取内存位置的值,而不能修改它。
- 字符串常量:在C语言中,字符串常量是只读的。如果您将一个字符串常量的地址赋给指针,您不能通过该指针来修改字符串的内容。
- 内存保护:操作系统可能会将某些内存区域标记为只读,以保护程序不被意外修改。这通常用于保护操作系统核心或其他重要数据结构。
- 静态存储区:在C语言中,静态变量和全局变量存储在静态存储区中。这些变量的内存位置可以被标记为只读,以确保程序的其他部分不会无意间修改它们。
需要注意的是,虽然指针指向的内存位置是只读的,但并不意味着整个指针都是只读的。您仍然可以通过更改指针本身的值来将其指向不同的内存位置。
栈区、堆区
栈区、堆区和文字常量区都是内存的不同分配方式。
栈区是一种由编译器自动分配和释放的内存区域,用于存储函数的参数值、局部变量和函数返回值等。栈区的内存分配方式是"先进后出",也就是说后进入的数据先被取出。
堆区是在程序运行时动态分配的内存区域,用于存储程序中动态创建的对象。堆区的内存分配方式是"先进先出",也就是说先进入的数据先被取出。
文字常量区是一个只读的内存区域,用于存储程序中的常量字符串或静态变量。文字常量区的内存分配方式由编译器完成,程序运行时不能修改其中的数据。
需要注意的是,栈区和堆区的内存管理由程序员手动管理,如果管理不当可能会导致内存泄漏或内存溢出等问题。而文字常量区的内存管理由编译器自动管理,程序员无法手动修改其中的数据。
转义字符
转义字符是一些特殊字符序列,用于表示非打印字符或具有特殊用途的字符。在许多编程语言和文本处理应用中都有使用。以下是一些常见的转义字符:
转义字符是一种特殊的字符序列,用于表示一些不可见字符或具有特殊意义的字符。当我们在编程语言或文本中遇到特殊字符时,我们可以使用转义字符来告诉解释器或编译器该如何处理这些字符。
- \n:换行符
- \t:制表符
- \":双引号
- \':单引号
- \\:反斜杠
- \r:回车符
- \b:退格符
- \f:换页符
这些转义字符可以在字符串中使用,以便在输出或处理时表示特殊的字符或序列。
memset函数
memset 是 C 语言标准库中的一个函数,用于将一块内存区域的每个字节设置为指定的值。
函数原型如下:
void *memset(void *ptr, int value, size_t num);
参数``解释:
- ptr:要设置值的内存区域的起始地址。
- value:要设置的值,以整数形式传递。通常是使用无符号字符表示的字节值。
- num:要设置的字节数。
函数返回一个指向 ptr 的指针,即设置值后的内存区域的起始地址。
memset 函数常用来初始化或清空一块内存区域。它将每个字节都设置为指定的值,因此可以用来初始化数组、结构体等数据结构。例如,可以使用 memset 将一个数组中的所有元素都设置为0,或将一个字符串中的所有字符设置为 \0。
memset函数用于将一段内存空间的值设置为特定的值。
其使用方法如下:
void *memset(void *ptr, int value, size_t num);
参数说明:
- ptr:指向要设置值的内存空间的指针。
- value:要设置的值。
- num:要设置的字节数。
返回值:指向被填充的内存空间的指针。
示例代码:
#include <stdio.h>
#include <string.h>
int main() {
char str[20] = "Hello, world!";
printf("Before memset: %s\n", str);
memset(str, '*', 5); // 将str的前5个字符设置为'*'
printf("After memset: %s\n", str);
return 0;
}
输出结果:
Before memset: Hello, world!
After memset: ***** world!
在示例中,memset(str, '*', 5) 的作用是将字符串str的前5个字符设置为*
以下是一个使用 memset 初始化数组的示例:
#include <stdio.h>
#include <string.h>
int main() {
int numbers[5];
memset(numbers, 0, sizeof(numbers));
for (int i = 0; i < 5; i++) {
printf("%d ", numbers[i]);
}
return 0;
}
以上代码使用 memset 将 numbers 数组的每个元素都设置为0。输出结果为: 0 0 0 0 0。
"->”
"->” 操作符是C++语言中的成员访问操作符,用于访问类的成员变量和成员函数。通过对象或指针使用”->"操作符可以访问对象的成员。例如,对于一个指向类对象的指针 ptr,可以使用ptr->member来访问该对象的成员。相当于(*ptr).member的简写形式。
使用结构体指针和指向结构体指针操作结构体对象
使用结构体指针和指向结构体指针的方法可以方便地操作结构体对象。下面是一个示例代码,演示如何使用结构体指针和指向结构体指针的方法操作结构体对象:
#include <stdio.h>
// 定义一个结构体
struct Person {
char name[20];
int age;
};
// 使用结构体指针操作结构体对象
void modifyPerson(struct Person *person) {
// 修改结构体成员的值
person->age = 30;
}
int main() {
// 创建一个结构体对象
struct Person person;
// 创建一个指向结构体的指针,并将其指向结构体对象
struct Person *personPtr = &person;
// 使用指针操作结构体对象
printf("请输入姓名:");
scanf("%s", personPtr->name);
printf("请输入年龄:");
scanf("%d", &(personPtr->age));
// 打印结构体对象的值
printf("姓名:%s\n", personPtr->name);
printf("年龄:%d\n", personPtr->age);
// 使用函数修改结构体对象的值
modifyPerson(personPtr);
// 打印修改后的结构体对象的值
printf("修改后的年龄:%d\n", personPtr->age);
return 0;
}
在上述示例代码中,首先定义了一个名为Person的结构体,包含姓名和年龄两个成员变量。然后,在main函数中,创建了一个结构体对象person,并通过结构体指针personPtr指向该对象。使用指针操作结构体对象时,可以使用->操作符来访问和修改结构体成员变量的值。在示例代码中,通过personPtr->name和personPtr->age分别访问和修改了结构体对象的姓名和年龄字段。
另外,示例代码还定义了一个名为modifyPerson的函数,接收一个指向结构体的指针作为参数。在函数内部,可以通过指针直接修改结构体对象的成员变量。在示例代码中,调用了modifyPerson函数,并将结构体指针personPtr作为参数传递给函数。函数内部将结构体对象的年龄字段修改为30。
最后,打印出了修改后的结构体对象的年龄值。
通过使用结构体指针和指向结构体指针的方法,可以方便地操作结构体对象的成员变量,对其进行读取和修改。
字节序 大小端 高字节 低字节 高地址 低地址
字节是计算机的基本单位,8bit=1byte,八位一字节,如果存储一个数,大于一个字节,由于计算机内存排布的不同,就要区分字节顺序:大端和小端
高字节、低字节
一个int类型的整数:123456789
最左边的叫高字节,即 0x07
最右边的叫低字节,即 0x15
高地址、低地址
在内存中,多字节对象都是被存储为连续的字节序列。例如在C语言中,一个类型为int的变量n,如果其存储的首个字节的地址为0x1000,那么剩余3个字节的地址将存储在0x1001~0x1003。总之,不管具体字节顺序是以什么方式排列,内存地址的分配一般是从小到大的增长。我们常把0x1000称为低地址端,把0x1003称为高地址端。
大端、小端
大端:高字节存放在低地址,低字节存放在高地址(大端从左往右,很符合人的思维)
小端:低字节存放在低地址,高字节存放在高地址(低放低,大端的逆序)
当形参是数组形式时,本质时同级别的指针
当形参声明为数组形式时,其本质是一个指针参数。在函数调用时,数组名将会被隐式转换为指向数组首元素的指针,并作为参数传递给函数。
实际上, C语言并没有直接支持将数组作为参数传递给函数的机制。相反,数组名被解释为指向数组首元素的指针。因此,当我们声明一个形参为数组类型时,它实际上是一个指向数组首元素的指针。
例如,考虑以下函数声明:
void printArray(int arr[], int size);
在这个声明中,参数arr被声明为一个整型数组。然而,在函数内部,arr将被解释为一个指向整型数的指针。实际上,该声明与以下声明具有相同的含义:
void printArray(int *arr, int size);
因此,在函数内部,我们可以使用指针操作符*来访问传递给函数的数组元素,并使用指针算术来遍历数组。
需要注意的是,虽然在函数调用时传递了数组的地址(即指向数组首元素的指针),但函数内部无法获取到数组的长度信息。因此,通常需要额外传递数组的大小作为参数,以便在函数内部进行正确的遍历操作。
位域
位域是一种数据结构中用来存储和操作位(bit)级别的字段的方法。它允许我们在一个字节(或更大的单元)中,将多个不同的布尔值或整数值存储在不同的位上,从而节省内存空间。
位域的使用可以通过在结构体或类定义中声明位域成员来实现。每个位域成员都有一个指定的位宽,该位宽决定了该成员所占的位数。在C语言中,我们使用特殊的语法来声明位域成员,如下所示:
struct { type member_name : width; };
其中,type表示要存储的数据类型,member_name是成员的名称,width是该成员所占的位数。
使用位域有几个注意事项:
- 位域成员的类型必须是整数类型(如int、char等),不能是浮点数类型。
- 位域成员不能被取地址、进行递增/递减运算或者进行sizeof运算。
- 位域成员的宽度不能超过其类型的位宽。
- 位域成员在内存中的布局和字节顺序可能因编译器和操作系统而异。
通过使用位域,我们可以在一些特定情况下优化内存使用,例如存储开关状态、标志位等。但需要注意的是,在多线程或跨平台的环境下,对位域的操作可能会存在一些问题,因此需要谨慎使用。
注意:
既然位域不能跨多个字节为什么位域的宽度可以超过8
位域的宽度可以超过8,是因为位域的宽度并不是以字节为单位来衡量的。位域的宽度实际上是以位为单位来衡量的,也就是可以指定位域占用的位数。
在C语言中,位域是一种用来表示内存中的位序列的数据结构。它允许我们在一个字节或多个字节中存储多个不同大小的字段,每个字段可以占用不同数量的位。这样可以有效地利用内存空间。
虽然位域不能跨多个字节,但是可以跨越单个字节的边界。当位域的宽度超过一个字节时,编译器会自动将其分配在下一个可用的字节中。
例如,如果一个位域的宽度为12位,那么它会占用两个字节,其中的前4位会占用第一个字节的高4位,后8位会占用第二个字节的全部8位。这样,我们就可以使用一个位域来表示一个12位的字段。
所以虽然位域不能跨多个字节,但是它们的宽度可以超过8,因为宽度是以位为单位来计算的,并且可以跨越单个字节的边界。