#include<dtdlib.h>中的qsort函数
qsort
是 C 语言标准库中的一个函数,用于对数组进行排序。这个函数非常灵活,因为它允许你通过函数指针来指定排序的比较函数,从而可以对任何类型的数据进行排序,只要这些数据可以通过某种方式进行比较。
qsort
函数的原型定义在 <stdlib.h>
头文件中,其基本形式如下:
void qsort(void *base, size_t num, size_t size, int (*compar)(const void *, const void *)); |
参数说明:
void *base
:指向要排序的数组的第一个元素的指针。size_t num
:数组中元素的数量。size_t size
:数组中每个元素的大小,以字节为单位。int (*compar)(const void *, const void *)
:一个指向比较函数的指针,该函数必须接受两个指向元素的const void*
类型的指针,并返回一个整数来指示这两个元素的顺序。如果第一个参数应该排在第二个参数之前,则返回负数;如果两个参数相等,则返回零;如果第一个参数应该排在第二个参数之后,则返回正数。
初始化指针和NULL的问题
int main()
{
int* a; // 声明了一个指向int的指针a,但没有初始化它
*a = 10; // 尝试解引用a并给它指向的内存位置赋值10,但a没有指向任何有效的内存
}
在C语言中,当你尝试对一个未初始化的指针 a
进行解引用(即使用 *a
)并给它赋值时,程序会报错,因为 a
没有指向任何有效的内存地址。未初始化的指针 a
包含一个不确定的值,这个值可能指向内存中的任何位置,包括受保护的内存区域或未分配的内存。因此,当程序尝试写入这个不确定的内存地址时,操作系统通常会阻止这种不安全的行为,导致程序崩溃或收到一个运行时错误。
那么如果初始赋了NULL值,会有错吗
int main() {
int* a = NULL;
*a = 10;
}
答案是依然有
程序会在尝试执行 *a = 10;
时崩溃或报错,因为 a
被初始化为 NULL
,它并不指向任何有效的内存地址。尝试通过 NULL
指针进行解引用(即使用 *a
)是未定义行为,但通常会导致程序崩溃,因为操作系统不允许程序写入 NULL
地址(即空指针)。
NULL
是一个宏定义,通常被定义为 (void*)0
或简单的 0
NULL和初始化的默认野指针完全不是同一个东西,NULL是为了消除野指针的问题
应该首先确保 a
指向一个有效的内存地址。这可以通过使用 malloc
来动态分配内存,或者让 a
指向一个已经存在的整数变量的地址来实现。
void S(struct TreeNode* aa, struct TreeNode* bb) {
a = b;
}int main() {
struct TreeNode* a,*b;//假设啊a,b都已经赋好了内存地址S(a,b);//无效
}
这里更多的是函数的原理问题, aa和a只是在地址这个值上相同。
要修改存了地址值的变量,还要再给这个变量用指针,
c结构体
c里没有bool,要自己加函数库
struct TreeNode {
int val;
struct TreeNode* left;
struct TreeNode* right;
};int main() {
struct TreeNode a,b;//结构体的赋值,是两块内存里的数据的传递,两块内存位置本身没有任何变化
a.val = 10;//执行完本行后为图1
b = a;
}
还有几种问题:
解引用
地址的不可直接移动(任何类型)能改的只有指向的值
在C或C++等语言中,直接交换两个int
变量的"地址"实际上是不可能的,因为int
类型通常存储在栈(stack)上,并且每个int
变量都有一个固定的地址,这个地址在变量的整个生命周期内是不会改变的。
当你尝试“交换”两个int
变量的值时,你实际上是在交换它们存储的数据,而不是它们的地址。
所以在各种数据类型*的使用中,能改的始终只有定义的*指针类型,从而修改处理各种值,以达到类似修改了地址的目的
你不能直接修改一个变量的地址,只可以通过指针来间接地修改它所指向的内存位置的值,或者改变指针本身所存储的地址值
c和c++和c#中的switch
在C、C++和C#中,switch
语句的case
块之间默认都存在穿透行为,即如果某个case
块中没有break
语句来阻止穿透,那么程序将继续执行下一个case
块中的代码
- C:在C语言中,
switch
语句的参数类型通常是整数类型(如int
、char
等)或枚举类型。它不直接支持字符串或浮点数类型的参数。 - C++:C++中的
switch
语句与C类似,其参数也必须是整数类型(包括字符和枚举值,因为它们在C++中也被视为整数)或可以隐式转换为整数的类型。C++同样不支持直接将字符串或浮点数作为switch
的参数。 - C#:C#中的
switch
语句则更加灵活,其参数类型可以是任何类型,包括整数、浮点数、字符、字符串以及枚举等。这使得C#的switch
语句在处理不同类型的数据时更加方便。
#include<string.h>的问题
简单功能了解
1. 字符串长度
- strlen():计算字符串的长度,不包括终止的空字符(
\0
)。 -
size_t strlen(const char *str);
2. 字符串复制
- strcpy():将源字符串复制到目标字符串中,包括终止的空字符。使用时需要注意目标字符串有足够的空间来容纳源字符串。
char *strcpy(char *dest, const char *src);
- strncpy():与
strcpy()
类似,但允许指定最多复制的字符数,以防止缓冲区溢出。如果源字符串长度小于n
,则目标字符串的剩余部分将被填充为\0
,除非源字符串本身更长但复制被截断。 char *strncpy(char *dest, const char *src, size_t n);
3. 字符串连接
- strcat():将源字符串连接到目标字符串的末尾,并包括终止的空字符。。使用前需要确保目标字符串有足够的空间来容纳两个字符串连接后的结果。
char *strcat(char *dest, const char *src);
- strncat():与
strcat()
类似,但允许指定最多连接的字符数,以防止缓冲区溢出。如果源字符串在达到n
之前遇到\0
,则连接将在此处停止。 char *strncat(char *dest, const char *src, size_t n);
4. 字符串比较
- strcmp():比较两个字符串,根据 ASCII 值进行逐字符比较。如果
s1
和s2
字符串相等,则返回 0;如果s1
在字典序上小于s2
,则返回负数;如果s1
在字典序上大于s2
,则返回正数。 int strcmp(const char *s1, const char *s2);
- strncmp():与
strcmp()
类似,但只比较字符串的前n
个字符。 int strncmp(const char *s1, const char *s2, size_t n);
5. 字符串搜索
- strchr():搜索字符串中第一次出现的指定字符,并返回指向该字符的指针。如果未找到字符,则返回
NULL
。 char *strchr(const char *str, int c);
- strrchr():与
strchr()
类似,但搜索的是字符串中最后一次出现的指定字符。 - strstr():搜索字符串中第一次出现的子字符串,并返回指向该子字符串的指针。函数原型为
char *strstr(const char *str1, const char *str2);
。如果未找到子字符串,则返回NULL
。
malloc的问题
要记得引用malloc头文件,不引用用不了
出现乱码时大概率是数据块里有数据没用上
问:用char*t=malloc(sizeof(char)*len);来存储“abcd”字符串,则len应该是多少?
len
应该是字符串的长度加上一个额外的字符用于存储字符串的终止符 '\0',len
应该是 5
问:为什么用4个也行?
虽然在某些情况下,如果你使用 malloc
为一个字符串分配了仅足够存储其字符(不包括终止符 '\0'
)的内存大小,并且你非常小心地不越界写入这个内存区域,程序可能不会立即崩溃或显示错误。然而,这种做法是非常危险和不可取的,因为它违反了 C 语言中字符串的标准表示方式,即字符串必须以空字符 '\0'
结尾。
当你尝试将这个未正确终止的字符串传递给期望以 '\0'
结尾的字符串处理函数(如 printf
的 %s
格式说明符、strlen
、strcpy
等)时,这些函数会继续读取内存直到它们遇到一个 '\0'
。由于你的字符串没有被正确终止,这些函数可能会读取超出你分配的内存范围的内存,导致未定义行为,包括但不限于:
- 读取到随机数据,导致输出不可预测。
- 读取到敏感数据,造成安全漏洞。
- 读取到程序的其他部分或操作系统的内存,导致程序崩溃。
- 触发访问违规(segmentation fault)或类似的错误。
此外,即使你的程序在当前环境下看似正常工作,由于内存布局和分配方式的细微差异,它在不同的机器上、在不同的时间点运行,或者在使用不同的编译器和编译选项时,都可能表现出不同的行为。
因此,即使你在某些情况下能够“侥幸成功”地使用 4 个字节的内存来存储一个应该需要 5 个字节(包括终止符)的字符串,你也不应该这样做。始终遵循标准做法,为字符串的终止符分配额外的空间,并确保你的字符串被正确终止。这样做将提高你的程序的健壮性、可移植性和安全性。
问:第五个位置我需要手动赋值为'\0'吗
需要。malloc函数只分配内存,不处理里面的数据,并返回一个指向该内存块的指针。它不会初始化这块内存中的任何数据,分配的内存块中的内容都是未定义的(通常是随机的或者之前遗留在内存中的旧数据)。
与const又有一些关系,扯上了内存块的问题
char*t=“123”;//正确
char t[] = malloc(sizeof(char) * 3);//错误
char *t = malloc(sizeof(char) * 3);//正确
问1:malloc申请的内存空间赋给char*t既然可以修改t指针上的值,为什么=“123”就不能改?
char* t = "123";
这行代码定义了一个指向字符常量字符串"123"
的指针t
。这里的"123"
是一个字符串字面量,它在编译时被存储在程序的只读数据段中。因此,尝试通过指针t
修改字符串中的任何字符都是未定义行为,通常会导致程序崩溃或运行时错误,因为您试图写入一个只读内存区域。另一方面,
char *t = malloc(sizeof(char) * 3);
这行代码动态分配了一个足够存储3个字符(注意这里没有为字符串的null终止符分配空间,这通常是一个错误,除非您确实需要一个非null终止的字符数组)的内存块,并将返回的指针赋给t
。这块内存位于堆上,是可写的,因此您可以通过指针t
安全地修改内存块中的字符。
问2:既然malloc申请的内存空间上的值可以修改,为什么不能赋给char t[]?
-
类型不匹配:
malloc
函数返回一个void*
类型的指针,它指向分配的内存。而char t[]
是一个字符数组,不是指针。你不能直接将void*
类型的值赋给数组名(尽管数组名在表达式中会被转换为指向其首元素的指针,但这里是在进行初始化,情况不同)。 -
数组初始化:数组初始化是在编译时进行的,而
malloc
是在运行时动态分配内存的。你不能在数组声明的同时使用malloc
来初始化它。
string的问题
字符串的规定,必须以‘\0’结尾,以适应不同函数对字符串的相同定义,防止数据和内存泄露
数组可以转指针,但指针不能赋值给数组
char*动态字符串改不了里面的char,赋值只能整个赋值
char*字符串只能改值,改不了地址地址在声明的时候就定了
char* HanShu(char*a) {
char k[] = a;
return k;
}问题代码
问题分析
-
数组初始化错误:
char k[] = a;
这行代码是尝试用指针a
来初始化数组k
,这在C语言中是不允许的。在C语言中,不允许原因主要是类型不匹配和数组初始化规则的限制。
首先,类型不匹配是指
a
是一个指向常量字符的指针(const char*
),而k
是一个字符数组(char[]
)。尽管数组名(在大多数情况下)可以隐式地转换为指向数组首元素的指针,但指针并不能直接“转换”为数组或用于数组的初始化。 -
栈上局部变量的生命周期:如果忽略上述初始化问题,并假设
k
被正确初始化为一个字符数组(例如,通过复制a
指向的字符串到k
),那么k
仍然是一个在函数s
的栈帧上分配的局部变量。当函数s
返回时,其栈帧被销毁,包括在栈帧上分配的所有局部变量(如k
)。因此,返回的指针k
将指向一个不再有效的内存位置,这通常被称为“悬挂指针”或“野指针”。 -
同样不行:问题不在a是否在只读数据段永久存储(到程序结束),而是数组的赋值就是不能用指针,但const也的确涉及到和该问题有关的部分
char* s(const char*a) {
char k[] = a;
return k;
}
正确做法
如果您的目的是在函数内部创建一个字符串的副本,并返回这个副本的指针,您应该使用动态内存分配(如 malloc
或 calloc
)来分配内存,这样返回的指针就可以指向在函数外部仍然有效的内存区域。
c中的指针数组和数组指针
char(*s)[2] = sss;//char[2]的一个地址指针,指向char[2]的一个指针
char * ss [2] = { "bbb","aaa" };//char*的一个[2]维数组,名为ss
和指针相关的定义,偏向于强调指针的个体和平常的变量一样定义,让它脱离指针的特殊性
因此
char*a[2][3];//为char*指针的[2][3]组合
char(*a)[2][3];//需要强调为单个大指针,是char[2][3]结合体的一个指针,否则默认舍弃指针特殊性,趋向平常变量定义规则
c中的&
在C语言中,void s(char& a)
这种函数声明方式是错误的,因为C语言不支持引用(references)这一概念,这是C++语言中的特性。在C语言中只能用指针(pointers)
void s(char a) { char temp = &a; temp = 10; }
a也不会改为10,因为 &a
是一个取地址操作符,它返回的是 a
的内存地址(char*
类型的值)
另外,如果你想要在一个表达式中,使用 &&
表示连续两次取地址操作,这在c中是不被允许的,最多可以通过组合使用 &
符号来连续取两个不同变量的地址,&只能取一个数据的地址
Swap函数在c与c++
在C语言中,标准库(如C标准库)本身并不直接提供一个通用的swap
函数用于交换两个变量的值。
在C++中,swap
函数是标准模板库(STL)的一部分,它位于<algorithm>
头文件中
C++的std::swap
函数是一个模板函数,能够交换任何类型的两个值,只要这些类型支持赋值操作。
visual stduio里为什么文件的.cpp后缀改成.c就会变成c文件
原因分析
- 编译器识别:
- 编译器会查找具有
.cpp
、.cxx
、.cc
等扩展名的文件作为C++源代码进行编译。 - C编译器则主要查找具有
.c
扩展名的文件作为C语言源代码进行编译。
- 编译器会查找具有
- Visual Studio的集成:
- Visual Studio是一个集成开发环境(IDE):集成了编译器和多种工具,以支持多种编程语言的开发。
- 当你在Visual Studio中创建或修改文件时,IDE会根据文件的扩展名来决定使用哪个编译器或工具链来处理该文件。
- Windows系统的大小写不敏感性:
- 值得注意的是,Windows文件系统在默认情况下是不区分文件扩展名大小写的(尽管文件系统本身支持大小写敏感性,但这需要特别配置)。这意味着
hello.cpp
和hello.CPP
在Windows系统中被视为相同的文件。然而,Visual Studio和编译器在识别文件类型时,主要依赖于扩展名的内容(即字符),而不是其大小写。
- 值得注意的是,Windows文件系统在默认情况下是不区分文件扩展名大小写的(尽管文件系统本身支持大小写敏感性,但这需要特别配置)。这意味着
实际操作
- 如果你将一个文件的
.cpp
扩展名改为.c
,Visual Studio会将其识别为C语言文件,并相应地调整其语法高亮、代码补全、编译设置等。 - 这意味着编译器将按照C语言的规则来编译该文件,而不是C++的规则。这可能会导致编译错误,特别是如果文件中包含了C++特有的语法或特性(如类、模板、命名空间等)。
程序的只读数据段
在C语言中,字符串字面量(如 "aaa"
)在编译时被存储在程序的只读数据段中,并且对于相同的字符串字面量,编译器通常会优化以只存储一份副本。因此,当你多次调用 re
函数并返回相同的字符串字面量时,这些调用实际上会返回指向同一份数据(即同一个地址)的指针。
char* re(char* s) { | |
return "aaa"; | |
} | |
int main() { | |
re("sssaaa"); // 调用re | |
//两次返回的“aaa”都存在同一个地址的程序的只读数据段 | |
} |
只读程序段的数据即使得到了地址也不可以修改值,甚至可能导致未定义行为(通常是程序崩溃)