关键字
非安全函数:strlen,sizeof,strcpy,strncpy,strcat,strncat,strcmp,strncmp,strtok,memcpy,atoi, memmove,memset
安全函数:所有的非安全函数后缀 _s
排序函数:qsort
,对一维数组排序,二维数组排序,二维char数组排序
类型:size_t
本文所有函数参数定义来源于 Microsoft 文档
灵魂拷问
其实上述的函数都不是很难,但总是有一些小坑,所以我特意设置一些问题,如果你都答对的话基本上就都掌握了。
-
strlen
和sizeof
的返回值是什么类型?为什么要用这个类型? -
sizeof
一个指针变量(数组头),你得到的是它所代表的数组的大小,还是指针本身的大小? -
int arr[10]; sizeof(arr)
的答案又是什么? -
strcpy
和strncpy
source
会不会自动加上截止符? -
strcat
和strncat
source
会不会自动加上截止符? -
qsort
对二维数组排序怎么写,如果每排首个元素大小一样,就依次比下去。 -
strtok
实际用法 -
memmove
和memcpy
会把最后的截止符也复制么?二者的区别是什么? - 你除了用
memset
给数组赋值过0
,还赋值过其他值么?如果第二个参数是-1
会怎么样?1
呢?
int main()
{
int *arr = (int *)malloc(sizeof(int) * 10);
memset(arr, -1, sizeof(int) * 10); // 如果全都赋值 -1 会怎么样?
for (int i = 0; i < 10; i++) {
printf("%d\n", arr[i]);
}
}
int main()
{
int *arr = (int *)malloc(sizeof(int) * 10);
memset(arr, 1, sizeof(int) * 10); // 如果全都赋值1会怎么样?
for (int i = 0; i < 10; i++) {
printf("%d\n", arr[i]);
}
}
- 请问这条代码,
int arr[10] = {1};
,arr的每一项分别是什么? - 请问这个qsort的cmp函数有什么问题么?
int compare(const void *a, const void *b){
return (struct element *)a->val - (struct element *)b->val;
}
灵魂拷问答案
-
size_t
类型,后面有解释 -
sizeof
指针变量会得到这个指针本身的大小 -
int arr[10]; sizeof(arr)
会得到整个数组的大小 -
strcpy
会。
strncpy
取决于 n 大小,如果n小于源长度,那么不会。大于源长度,会。 -
strcat strncat
都会在结尾加上 截止符。 -
qsort
内容看后面。 -
memmove memcpy
是纯内存操作,看你复制了什么过去。不会主动加东西。 -
memset
memset填充,是以字节为单位,而不是以元素为单位。,这就是为什么你赋值0和-1都可以,但是1不行。因为赋值 -1 的时候,补码全都是1,最后也得到-1, 0也是一样。但是1不行。 -
arr
的第一项是1,但是后面都是0(在我的编译器上是这样的)。 - 优先级问题,强制类型转换优先级,小于
->
,所以程序会报错
size_t 类型
总结自 size_t 知乎的一篇文章,我觉得没必要研究的那么深入,先把他搞清楚是什么,怎么用,然后自己开始学着用就行了。
个人理解是,把它当成 unsigned int
就行了,这是一个无符号数。C标准使用size_t
的初衷是,为了方便移植。比如说在一个32位系统上,size_t
代表了unsigned int
,但是一到了64位系统上,它就适应系统,变成了unsigned long
。因为size_t的取值range是目标平台下最大可能的数组尺寸。在我的电脑上,返回的是unsigned long long
,每次printf都得用 "%llu"
。
qsort 快速排序
void qsort(
void *base,
size_t number,
size_t width,
int (__cdecl *compare )(const void *, const void *)
);
int cmp(const void *a, const void *b);
- 为了让qsort可以对不同类型的指针进行排序,第一个参数是void类型的指针,不管是什么类型的指针,都可以被强制类型转换为void型的指针。这也解释了为什么qsort要使用第三个参数 width,因为不同类型的数组,每个元素占的大小是不同的,而qsort不清楚它需要每隔多少个字节去获得一个元素。
- 最后一个参数cmp,是一个指向函数的指针。cmp的两个参数是待比较的,如果第一个数大于第二个数,正序排序,反之亦然。函数名作为参数时就是函数的地址,所以最后一个参数直接写cmp就好。
- cmp函数的参数也是void类型的指针,也好理解,是为了让qosrt有通用性,第一个参数都是 void *base,cmp也得是void。
- qsort对一维double数组排序,cmp写法
int cmp (const void *p1, const void *p2){
const double *a1 = (const double *)p1; // 因为p1的类型时void, 所以需要先强制类型转换成 double
const double *a2 = (const double *)p2;
return *a1 - *a2;
}
int cmp (const void *p1, const void *p2){
return *((const double *)p1) - *((const double *)p1); // 写的简短一点就这样子
}
- qsort对二维double数组排序,cmp写法。
有一块经常报错,记得qsort本体的第三个参数要改一下,因为这个是排和排之间排序,所以得把要排序的元素搞清楚
qsort(arr, arrSize, sizeof(arr[0]), cmp);
如果只是想对第一个元素排序,那就用下面的代码。
int cmp(const void *p1,const void *p2)
{
const int *a1 = (const int *)p1;
const int *a2 = (const int *)p2;
return *a1 - *a2;
}
下面这种排序是,排序每排的第一个元素,如果一样大,再排后面的。 但是这个代码有很大问题,假设第一排和第二排元素完全一致,这排到最后怕是要溢出。我自己试了一下,倒是没有报错,也不知道怎么回事。将来再探究下。
int cmp(const void *p1,const void *p2)
{
const int *a1 = (const int *)p1;
const int *a2 = (const int *)p2;
if(*a1 != *a2)
return *a1 - *a2;
return cmp(a1 + 1, a2 + 1);
}
- qsort对二维char数组排序
int cmp(const void *p1,const void *p2)
{
const char *a1 = (const char *)p1;
const char *a2 = (const char *)p2;
return strcmp(a1,a2);
}
- qsort 对结构体struct的一项进行排序
有个小坑要注意一下,强制类型转换的优先级,是小于->
符号的优先级的,所以cmp里面记得多包括号
int compare(const void *a, const void *b){
return ((struct element *)a)->val - ((struct element *)b)->val;
}
strlen and sizeof
size_t strlen(
const char *str
);
把他们放到一块说,是因为他们的返回值,都是size_t,也就是说,都是无符号数。
他们都可以算一个字符串的长度,strlen
不会把截止符'\0'算进去,但是sizeof
会。
当然sizeof
的用处更大一点,可以直接计算出来一个类型占据的空间。
不过sizeof也有个小坑,那就是不要使用sizeof对指针变量求值。如果你要用memset
对一个被malloc
出来的整型数组赋值,那么你可必须要用 sizeof(int) * arrSize
而不是 sizeof(arr)
。因为在当前
int main()
{
int a[10];
int *arr = (int *)malloc(sizeof(int) * 10);
printf("%llu\n", sizeof(a)); // 这个是 40
printf("%llu", sizeof(arr)); // 这个是 8,sizeof计算了int 类型指针变量的大小
}
strcpy and strncpy
下面是他们的定义
char *strcpy(
char *strDestination,
const char *strSource
);
char *strncpy(
char *strDest,
const char *strSource,
size_t count
);
strncpy
的第三个参数的意思是,从source
复制 n
个字符到str
,比strcpy
智能一点,但是也不能保证这个 n
比destination
就小,所以想达到真正的安全,还是得用安全函数。
另外strcpy
会包含截止符'\0',但是 strncpy
取决于参数 n 的大小。
如果 第三个参数 n 小于source
长度,那么只会把 n 个字符给整进去,不会以 '\0' 结尾,你要自己加。
如果n 大于 source
长度,那么会把整个source都给整进去,多出来的用截止符填充,所以用 '\0' 结尾。
strcat and strncat
char *strcat(
char *strDestination,
const char *strSource
);
char *strncat(
char *strDest,
const char *strSource,
size_t count
);
上面那俩是直接覆盖,这两个是拼接,把source拼到destination后面。当然,都不安全。strncpy也就图一乐,真要安全还得安全函数。哈哈哈哈哈哈哈。
strcat
的source
会直接把dest的截止符'\0'给覆盖了,最后自带一个截止符。
strncat
和strncpy
不一样,不管n的大小,结尾自动带一个截止符'\0'。但是strncpy
如果n
过大,会填充截止符,但是strncat不会填充。
memmove and memcpy
void *memcpy(
void *dest,
const void *src,
size_t count
);
void *memmove(
void *dest,
const void *src,
size_t count
);
名字里一带 “mem”这说明是在内存层面上操作了。
所以就看第三个参数 n ,n是多大,就复制了多少字节过去。
内存操作不自动添加截止符,所以就看n的大小,有没有把截止符复制过去。
memmove
相对来说比memcpy
安全一点,因为它先把src
复制到一个temp
里面,然后再复制过去。所以避免了两个指针空间重叠的问题。缺点就是浪费资源,所以酌情使用。
memset
void *memset(
void *dest,
int c,
size_t count
);
切记,memset填充,是以字节为单位,而不是以元素为单位。
int main()
{
int *arr = (int *)malloc(sizeof(int) * 10);
memset(arr, 1, sizeof(int) * 10); // 如果全都赋值1会怎么样?
for (int i = 0; i < 10; i++) {
printf("%d\n", arr[i]);
}
}
memset之后的存储空间的值是:
0x01010101 0x01010101 0x01010101 0x01010101 0x01010101 0x01010101 0x01010101 0x01010101 0x01010101 0x01010101
strtok
char *strtok(
char *strToken,
const char *strDelimit
);
下面直接给一个strtok的具体用法
int main(){
char str[20] = "Nov 10th 2020";
char *word;
char **str2 = (char **)malloc(sizeof(char *) * 3);
word = strtok(str, " ");
int i = 0;
while (word != NULL) {
printf("%s\n", word);
str2[i] = word;
word = strtok(NULL, " ");
i++;
}
for (int i =0; i < 3; i++) {
printf("%s\n", str2[i]);
}
printf("now, str turns to %s", str); // 这条代码为了检验 str是否被改变, 事实上他被改变成了 Nov
return 0;
}
它的用法就是,第一次使用,把一个字符数组作为参数放进去,第二个参数输入词和词之间的间隔符,然后就能获得第一个间隔符前面的字符串。之后一次使用要在while里面,把NULL循环作为第一个参数,继续获得word
,直到获得的最后一个word
是截止符'\0'
,while
中断,分割完成。
其实我也不知道为什么strtok
需要在 while
循环里面用一个 NULL
来做第一个参数,但是,它就是这么用的。咱们先记住,先会用,再研究。
strtok
的一个常见错误是,把常量赋值给指针,然后再作为strtok里面的第一个参数,因为该函数会改变字符串,所以会报出改变常量的 “Segmentation fault”错误。
所以不要把char str[20] = "Nov 10th 2020";
改成 char *str = "Nov 10th 2020";
atoi
int atoi(
const char *str
);
使用很简单,把字符串扔进去,然后返回int,注意用的时候别溢出int
int main(void) {
char *str = " 110N2020NOV";
//char str[20] = " 110N2020NOV"; // 不管是字符数组还是指针,二者均可执行
int val = atoi(str);
printf("%d", val);
}
但是我做了几个实验,我发现,第一个数字之前,可以有空格,但是不能有字母,这样子它才能读取到第一个整数,如果它先看到字符串的话,最后 val
返回值为 0。如果它看到了数字,那么遇到第一个不是数字的字符,读取就结束,返回已经读取的东西。