C语言_指针_入门

本章内容:

  1. 什么是指针
  2. 指针的定义
  3. 指针的应用
  4. 指针的运算
  5. 指针与数组的关系
  6. 指针和数组,函数示例
  7. 二级指针

什么是指针

计算机中所有的数据都必须放在内存中,不同类型的数据占用的字节数不一样,例如int占用 4 个字节,char 占用 1 个字节。为了正确地访问这些数据,必须为每个字节都编上号码,就像门牌号一样,每个字节的编号是唯一的,根据编号可以准确地找到某个字节。 我们将内存中字节的编号称为地址(Address)或指针(Pointer)。地址从 0 开始依次增加,对于 32 位环境,程序能够使用的内存为 4GB。
最小的地址为0x0000 0000,最大的地址为0XFFFF FFFF
image-20210720094948057

对星号*的总结

  1. 表示乘法。
  2. 表示定义一个指针变量,以和普通变量区分开
  3. 表示解引用。获取指针指向的数据,是一种间接操作。

*号运算符所在的环境不一样,*号的含意也不一样。

1
2
3
4
5
6
7
8
9
10
11
int main()
{
int a = 10, b = 20;
int c = a * b;//表示乘号

int * p;//声明指针
p = &a;
* p = 100;//解引用

return 0;
}

定义指针变量

一定要在定义时初始化

要么初始化为空,要么指向已有变量的地址。否则指针指向的位置不可控,可能在操作指针时会改变其他正在运转的值。

1
2
3
4
5
6
7
8
9
10
int main()
{
int *p , s; // p 是整型指针变量,s 是整型变量。
char *cpa , *cpb; // cpa 和 cpb 都是char 类型指针变量。
int a = 10;
// 以下两句与 int *p = &a; 等价
int * ip; // 未初始化的指针,不良的定义习惯
ip = &a;
return 0;
}

图解

image-20210720101404483
定义指针变量时,类型对指针变量起到2个作用:

  1. 解析存储单元的大小;
  2. 指针变量加1的能力。

如:

  1. double da = *dp;
  2. int ib = *ip;
  3. char cc = *cp;

综合实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void fun(int *p)
{
int a = 200;
*p = 100;
p = &a;
}
int main()
{
int x = 0;
int *s = &x;
fun(s);
printf("%d %d\n",x,*s);//x=100, *s=200或失效,因为int a在fun函数调用后释放了。错!*s是100。
return 0;
}

image-20210720105039607

指针的运算

指针+1

typename * p;
p = p + 1;被编译器解释成:p = p + sizeof(typename) * 1;

示例1:int指针变量加1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>
int main()
{
const int n = 5;
int ar[n]={12,23,34,45,56};
int *ip = &ar[0]; // int *ip = ar;
for(int i = 0;i<n;++i)
{
printf("0x%08X => %d \n",ip,*ip);
ip = ip + 1;
}
printf("\n");
return 0;
}

image-20210720110054100

示例2:char类型指针变量+1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<stdio.h>
int main()
{
const int n = 5;
char ar[n]={'t','u','l','u','n'};
char *cp = &ar[0]; // char *cp = ar;
for(int i = 0;i<n;++i)
{
printf("0x%08X => %c \n",cp,*cp);
// printf("%#p => %c \n",cp,*cp);
cp = cp + 1;
}
printf("\n");
return 0;
}

image-20210720110059739

示例3:double类型指针变量+1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<stdio.h>
int main()
{
const int n = 5;
double ar[n]={1.2, 2.3, 3.4, 4.5, 5.6};
double *dp = &ar[0]; // char *cp = ar;
for(int i = 0;i<n;++i)
{
printf("0x%08X => %f \n",dp,*dp);
// printf("%#p => %f \n",dp,*dp);
dp = dp + 1;
}
printf("\n");
return 0;
}

指针+1详解

image-20210720110426346

思考下面代码运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
int ar[5]={12, 23, 34, 45, 56 };
int *p = ar; // int *p = &ar[0];
int x = 0;
int y = 0;
x = *p++; //int x = *p; p++;
y = *p;
printf("%d %d \n",x,y);
x = ++*p;
y = *p;
printf("%d %d \n",x,y);
x = *++p;
y = *p;
printf("%d %d \n",x,y);
return 0;
}

image-20210722152558428

image-20210722152657523

总结

指针的类型决定了两个能力

  1. +1的能力,偏移量。
  2. 对内存的解析能力。

image-20210722154957018

1
ix=0x12345678  sx=0x5678 cx=0x78

指针有两个要素

  1. 地址值
  2. 指针指向的数据类型

指针与数组的关系

  1. 数组名被看作该数组的第一个元素在内存中的首地址(仅在sizeof操作中例外,该操作给出数组所占内存大小)。
  2. 数组名在表达式中被自动转换为一个指向数组第一个元素的指针常量。
  3. 数组名是指针,非常方便,但是却丢失了数组另一个要素:数组的大小,即数组元素的数量。编译器按数组定义时的大小分配内存,但运行时(run time)对数组的边界不加检测。这会带来无法预知的严重错误。

image-20210720111145131

C提供根据数组的存储地址访问数组元素的方法。上图中ar是数组第一个元素的地址,所以*ar是数组的第一个元素ar[0],而ar+1是数组第二个元素的地址,*(ar+1)是第二个元素ar[1]本身。指针加1,则地址移动一个数组元素所占字节数。

C语言的下标运算符[]是以指针作为操作数的,ar[i]被编译系统解释为*(ar+i),即表示为ar所指(固定不可变)元素向后第i个元素。无论以下标方式或指针方式存取数组元素时,系统都是转换为指针方法实现。逻辑上有两种方式,物理上只有一种方式。

数组名访问

1
2
3
4
5
6
7
8
9
10
int main()
{
const int n = 5;
int ar[n] = {1, 2, 3, 4, 5};
for(int i = 0;i < n;++i)
{
printf("0x%08x %d %d \n",ar+i, ar[i], *(ar+i));
}
return 0;
}

image-20210720111509965

指针访问

1
2
3
4
5
6
7
8
9
10
11
int main()
{
const int n = 5;
int ar[n] = {1,2,3,4,5};
int *p = ar;
for(int i = 0;i<n;++i)
{
printf("0x%08x %d %d\n",p+i,p[i],*(p+i));
}
return 0;
}

image-20210720111538163

数组的退化

用数组作为函数的形参,数组将退化为指针类型。

如果想要在函数中传递一个一维数组作为参数,必须以下面三种方式来声明函数形式参数,这三种声明方式的结果是一样的,因为每种方式都会转成指针。

1
2
3
void Print_Array(int br[],int n); //形式参数是一个未定义大小的数组:
void Print_Array(int br[5],int n); //形式参数是一个已定义大小的数组
void Print_Array(int *br,int n)//形式参数是一个指针

要点:为什么数组作为函数的形参会退化为指针呢?我们将从时间效率和空间效率上分析。

我们先假设数组作为函数的形参,我们分析一下调用过程;

1
2
3
4
5
6
7
8
9
10
11
12
void Print_Array(int br[5],int n)
{
int ar_len = sizeof(br);
printf("ar_len : %d \n",ar_len);
}
int main()
{
const int n = 5;
int ar[n] = {1,2,3,4,5};
Print_Array(ar,n);
return 0;
}
1
2
3
4
5
6
#include<stdio.h>
void Print_Ar(int br[10])//如果如此定义,传参数前,主函数开辟了数组空间,传参数时,栈区又会开辟10个空间!而且又要浪费时间把数组中的数据依次传入。既浪费空间又浪费空间。
{
int size=sizeof(br);//只会是4,因为(int br[10]退化成了(int *br)
printf("size: %d \n",size);
}

二维数组的退化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//打印二维数组
void Print_Ar(int(*br)[4],int row,int col)
{
//int n = sizeof(**br);//虽然可以通过sizeof(**br);算出column即列数。但为了使程序更加方便化,定义形参col
printf("%d %d\n", row, n);
for (int i = 0;i < row;++i)
{
for (int j = 0;j < col;++j)
{
printf("%d ", br[i][j]);
}
printf("\n");
}
printf("\n");
}

如果二维数组的形参定义为:int [4][5],同一维数组的道理一样,将会退化为int(*br)[4]

数组名表示数组首元素的地址

数组名表示数组首元素的地址,而不是数组的地址。

数组的地址

1
2
3
4
5
6
7
8
9
10
int main()
{
const int n =5;
int ar[n] = {12,23,34,45,56};

int* ip = ar;//&a[0]
int (*s)[5] = &ar;//
int* pa[5];
return 0;
}
1
2
3
4
5
6
7
8
9
int main()
{
int a = 10;
int* p = &a;
//p: int*, *p: int
int a[5];
int(*p)[5] = &a;
//p: int [5]*, *p: int [5]
}

指针与字符串

1
char c = "xcgong"[2];//此句话编译后等效于:字符串数组的首元素地址即'y'的地址p,+2取值后*(p+2)赋值给c,即'g'。

总结

访问数组时,编译器会把x[y]转换为:*(x+y),对应机器码的基变址寻址。x是基地址。

const

不带const修饰的指针是自由的,一是自身的值可以改变,二是指向的值可以改变。

1
2
3
4
int a = 10, b = 20;
int *p = &a;
*p = 100;
p = &b;

带const

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int a=10,b=20;
//以下的const在*的左边,修饰的是指向能力(修饰“修改指向的值”的能力),两句话等价
const int *p = &a;
int const *p = &a;
*p = 100;//error
p = &b; //OK
//以下的const在*的右边,修饰指针自身,可读不可改变自身的值
int * const p = &a;
int x = *p;
*p = 100;//OK
p = &b; //error
//有两个const,第一个修饰的是指向能力(修饰“修改指向的值”的能力),第二个修饰的是指针自身
const int * const p = &a;
int const * const p = &a;
*p = 100;//error
p = &b; //error
1
2
3
4
5
6
7
int a=10;
const int b=20;
int *pa=&a;//*pa=100;//ok
const int* pa1=&a;//*pa1=100;//error

int* pb=&b;//error
const int* pb=&b; //ok
1
2
3
4
5
6
7
8
9
10
11
12
//.cpp下编译
int main()
{
const int a=10;
int b=0;
int* p=(int*)&a;
*p=100;
b=a;//编译时自动把a替换成了10
printf("a=%d\n",a);//打印10
printf("b=%d\n",b);//打印10
printf("*p=%d\n",*p);//打印100
}

str相关函数

strlen

第一次编的

1
2
3
4
5
6
7
8
9
10
#include<stdio.h>
int my_strlen(char* string)
{
int count = 0;
while (*string++ != '\0')
{
++count;
}
return count;
}

老师的改进

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
#include<assert.h>
int my_strlen(const int* string)
{
assert(string != NULL);
int i=0;
while(string[i]!='\0')
{
++i;
}
return i;
}

另有方法:利用指针地址计算

指针地址的计算——算头不算尾

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
#include<assert.h>
int my_strlen(const int* str)
{
assert(str!=nullptr);
const char* cp = str;
while(*cp!='\0')
{
cp++;
}
return (cp-str);
}

变种问题

不让用计数变量计算字符串长度–指针地址计算

不让用任何变量计算字符串长度–递归函数

1
2
3
4
5
6
7
8
9
int my_strlen2(const char* string)
{
assert(string != nullptr);
if (*string++)
{
return my_strlen2(string)+1;
}
else return 0;
}

cpy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
char* my_strcpy(char* dest,const char* src)
{
assert(dest != NULL && src != NULL);
char* cp = dest;
while (*src != '\0')
{
*dest = *src;
dest++;
src++;
//也可以一句话解决,while(*dest++ = *src++);//因为赋值语句有返回值是赋值的值,但可读性不好
}
*dest = '\0';
return dest;//返回dest即目的拷贝字符串,便于函数嵌套达到可以实现连续拷贝
}

对于*dest++ = *src++可以看一下汇编代码,就清楚了
image-20210723160515838

image-20210723160525560

strcat

定义一个指针变量

1
2
3
4
5
6
7
8
9
10
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest != nullptr && src != nullptr);
char* cp = dest;
while (*cp != '\0') ++cp;
while (*cp++ = *src++);
return dest;
}

不用定义指针变量,利用len获取下标

1
2
3
4
5
6
7
8
9
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest != nullptr && src != nullptr);
int index = my_strlen(dest);
my_strcpy(dest+len, src);
return dest;
}

strcmp

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
char* stra = "yhp";
char* strb = "yhp";

char strc[]="yhp";
char strd[]="yhp";

bool x = (stra==strb);//1
bool y = (strc==strd);//0
printf("x = %d y = %d\n",x,y);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<stdio.h>
#include<assert.h>
int my_strcmp(const char* ap, const char* bp)
{
//ap="yhping" bp="yhping" 0
//ap="yhxing" bp="yhping" >0
//ap="yhping" bp="yhxing" <0
assert(ap!=nullptr&&bp!=nullptr);
while((ap||bp)&&(*ap==*bp))
{
ap++;
bp++;
if(*ap=='\0'||*bp=='\0')break;
}
return *ap-*bp;
}
int main()
{
char stra[10] = "yhping";
char strb[10] = "yhxing";
int ans = my_strcmp(stra,strb);
printf("%d\n", ans);
}

练习

判断当前系统是小端地址还是大端地址(可通过指针强转来探测)

1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
short st = 0x0001;
char* cp = (char*)&st;
if(*cp==0x01)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
}

不让用任何变量计算字符串长度–递归函数

1
2
3
4
5
6
7
8
9
int my_strlen2(const char* string)//不定义变量,递归求解
{
assert(string != nullptr);
if (*string++)
{
return my_strlen2(string)+1;
}
else return 0;
}

给出以下的代码输出结果

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
char* stra = "yhp";
char* strb = "yhp";

char strc[]="yhp";
char strd[]="yhp";

bool x = (stra==strb);//1
bool y = (strc==strd);//0
printf("x = %d y = %d\n",x,y);
}

strcmp函数

1
2
3
4
5
6
7
8
9
10
11
12
13
//ap="yhping" bp="yhping" 0
//ap="yhxing" bp="yhping" >0
//ap="yhping" bp="yhxing" <0
int my_strcmp(const char* ap, const char* bp)
{
assert(ap != nullptr && bp != nullptr);
while (ap&&bp&&*ap == *bp)//有一个不为空且依次相等
{
ap++;
bp++;
}
return *ap - *bp;
}

冒泡排序

1

二分查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int FindValue(int* ar,int n,int val)
{
assert(ar != nullptr);
int pos = -1;
int left = 0, right = n - 1;
//为什么left还要等于right呢?left和right表示数据的规模,如果left=right说明还要再寻找一次。当right和right错位,说明没有数据了。
while (left<=right)
{
int mid = (left + right) >>2;
if (val < ar[mid])
{
right = mid-1;//val值在mid下标前,mid下标已经没必要比较,而且必须-1,因为如果这个值在数组中没有,而在mid下标值和mid-1下标值之间的话
}
else if (val > ar[mid])
{
left = mid+1;
}
else
{ //如果有重复的值,则向前探测相同值。
//但也要注意越界问题。mid-1不要<0
while (mid > left && ar[mid - 1] == ar[mid])
{
--mid;
}
pos = mid;
break;
}
}
return pos;
}
  1. 如果数据量非常大,left+right可能会溢出,怎么解决——left + (right - left) / 2
  2. 线性探测如何优化,以加速探测速度。
  3. 二分查找及其变种:斐波那契查找的时间复杂度还是O(log2n)O(\log_2 n),但是 与折半查找相比,斐波那契查找的优点是它只涉及加法和减法运算,而不用除法,而除法比加减法要占用更多的时间。

循环移动数组

示例:int ar[10]={1, 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10};
右移一个数据元素:输出{ 10,1,2,3,4,5,6,7,8,9};
右移k个数据元素:如k = 3;输出{8,9,10,1,2,3,4,5,6,7}

实现函数:
Right_Move_Array; // 右移一个数据元素
Right_Move_Array_K; // 右移k 个数据元素
Left_Move_Array;
Left_Move_Array_K;

https://blog.csdn.net/weixin_45007066/article/details/116057402
https://blog.csdn.net/weixin_45332776/article/details/116333199

改进冒泡排序

1

my_memset()

1
2
3
4
5
6
7
8
9
void my_memset(void* src, int val, int size)
{
assert(src != nullptr);
char* cp = (char*)src;
for (int i = 0;i < size;++i,++cp)
{
*cp = 0;
}
}

my_memmove()

1

my_atoi()

字符串数字转换为整型数字

  1. "123"=>123
  2. "-123"=>-123
  3. "0123"(8进制)=>
  4. "0x123Df"=>
  5. "75894235702389573478903242334537" =>
  6. “505"错写为了"5o5”,要有纠错能力转为505
1

Redis_SDS

SDS指simple dynamic string,即简单动态字符串。

Memcached与Redis的区别和选择

Memcached与Redis的区别和选择https://blog.csdn.net/qq_18671415/article/details/104540628

https://zhuanlan.zhihu.com/p/183993817

Redis 这几年的大热,现在已经替代 Memcached 成为缓存技术的首要中间件,作为大厂的带头兵,在 BAT 里面,Redis 也已经逐渐取代了 Memcached,广泛使用 Redis 作为缓存应用方案。

1)速度更快

Memcached 使用的是多线程模型,既然是多线程,就会因为全局加锁而带来性能损耗。而 Redis 使用的是单线程模型,没有锁竞争,速度非常快。

2)数据类型更丰富

Memcached 数据类型非常单一,只支持 String 数据类型,在业务实现上就非常有瓶颈。

而 Redis 支持 string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(sorted set:有序集合) 等……丰富的数据类型可以让 Redis 在业务上大展拳脚。

这也是 Redis 能代替 Memcached 最重要的原因之一。

并且,Memcached 值最大上限为:1M,而 Redis 最大可以到:1GB。

3)数据持久化

Memcached 不支持持久化,Redis 支持。

缓存服务器断电后,Memcached 的数据是不能恢复的,而 Redis 可以将数据保久化在磁盘中,服务器重启的后可以加载再次使用,不会造成数据断电丢失。

比如,有些数据是直接放在缓存数据库中的,其他地方可能没有备份,如果丢失了,那可能会造成业务影响,这也是 Redis 非常有用的一个保障特性。

柔性数组

先看一个结构体的设计

1
2
3
4
5
6
7
8
#define MAXLEN 1024 //1K  //对于操作系统层面来讲,分配、管理内存是以一页为单位的,占4K即1024*4=4096字节
typedef struct kd_node
{
struct kd_node* left;
struct kd_node* right;
int dim;
unsigned long long data[MAXLEN];//数据 //占用了1024*sizeof(unsigned long long)=8k字节
}kd_node;

在上面这段代码中,为了存储数据,申请了长度为1024的unsigned long long型数组。若是数据的长度远远小于MAXLEN,这样的设计是很浪费空间的。

对于数组,我们的原则是大开小用(尽可能开辟足够的空间,但使用时不能全都用上)。而对于动态内存分配,我们就可以更加的灵活运用,但代价是使用完后要释放掉。

C99标准中给出了新的设计方法,通过柔性数组可以解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
struct sd_node
{
int num;//数据的长度,即已填入的数据占用的长度
int size;//总长度
char data[];//或char data[0];
//数组的大小声明为0或不给出大小,称之为柔性数组。必须在最后位置声明,且一旦声明后面不能再声明其他结构。全局数组和局部数组不可如此定义。
};
int main()
{
sizeof(struct sd_node);//8
}

在struct sd_node结构体中data仅仅是一个待使用的标识符,不占用存储空间。

用途:柔性数组的主要用途是为了满足长度可变的结构体。

用法:在一个结构体的最后,声明一个长度为0的数组,就可以使得这个结构体是可变长的。对于编译器来说,此时长度为0的数组并不占用空间,因为数组名本身不占空间,它只是一个偏移量。数组名这个符号本身代表了一个不可修改的地址常量。但对于这个数组的大小,我们可以进行动态分配。

注意:如果结构体是通过calloc/malloc/realloc等动态分配方式生成的,在不使用时需要释放相应的空间。

优点:比起在结构体中声明一个指针变量再进行动态分配的做法,柔性数组方法效率要高,因为简单。

缺点:在结构体中,数组为0的数组必须在最后声明,在设计结构体类型时有一定限制。

image-20210810021047383

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main()
{
struct kd_node* sp1 = (struct kd_node*)malloc(sizeof(struct kd_node)+20);
if(sp1==nullptr)
{
exit(EXIT_FAILURE);
}
strcpy_s(sp1->data,20,"yhping");//20指sp1->data的最大空间,使其安全拷贝
sp1->num = strlen("yhping");
sp1->size = 20;

printf("size: %d \n",sp1->size);
printf("num: %d \n",sp1->num);
printf("data: %s \n",sp1->data);

free(sp1);
sp1 = nullptr;
return 0;
}

image-20210810022209189

image-20210810022351037

易错

image-20210810023944795

image-20210810024031901

运行结果

image-20210810024054322

sizeof的运算只发生在编译时期,因此只计算变量的类型的大小,经典的例子就是sizeof(++a),再次输出a时发现没有+1,是因为sizeof(++a)根本没有执行。

与另一种实现方法的对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct kd_buff
{
int num;
int size;
char* buff;
};
int main()
{
kd_buff* p = (kd_buff*)malloc(sizeof(kd_buff));

p->buff = (char*)malloc(sizeof(char)*strlen("yhping"));
strcpy(p->buff,strlen("yhping"),"yhping");

p->num = strlen("yhping")-1;
p->size = strlen("yhping");
}

显然,指向字符串指针的方法的操作起来比柔性数组操作起来要繁杂一些。
image-20210810025606081

而且,这种结构需要分两次申请空间,导致到最后必须释放两次。而柔性数组只申请一次空间,最后释放时直接释放一次即可。因此,综合考虑,柔性数组明显是优于上述结构的。

退出方案

exit(), _exit(), return, abort()函数的区别
https://blog.csdn.net/lyf47/article/details/44203499

在Redis中我们采用的是abort();

1
2
3
4
5
static void adsOomAbort()
{
fprintf(stderr,"SDS: out of memory \n");
abort();
}

abort()

exit(EXIT_FAILURE)

_exit()

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
typedef sds char*;
void sdsclear(sds s)
{
struct sdshdr* sh=(struct sdshdr*)s-sizeof(struct*(sdshdr));
sh->free+=sh->len;
sh->len=0;
sh->buf[0]='\0';
}
sds sdscat(sds stra,sds strb)
{
struct sdshar* sh_a=(struct sdshdr*)stra-sizeof(struct*(sdshdr));
struct sdshar* sh_b=(struct sdshdr*)strb-sizeof(struct*(sdshdr));
if(sh_a->free>=str->free)
{
memcpy_s("")
}
stra[6]
}

string.h

memcpy

函数原型为void *memcpy(void *destin, void *source, unsigned n)。函数的功能是从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中,即从源source中拷贝n个字节到目标destin中。

C语言传统的字符串

即以空字符结尾的字符数组。

  1. 在.rdata数据区里
1
char* sp = "yhping";
  1. 数组存放
1
char stra[] = "yhping";
  1. 堆区开辟,并用strcpy_s(cp,10,“yhping”);复制
1
2
3
4
5
int main()
{
char* cp = (char*)malloc(sizeof(char)*10);
strcpy_s(cp,10,"yhping");
}

redis的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
typedef char* sds;
struct sdshdr
{
int len; //已输入的字符串不包括\0的长度
int free;//还未使用的字节数,同样不包括\0
char buf[];
}
//传入一个init指针(多为字符串)和其长度,长度不计算\0,memcpy写入内存时不包括\0的写入,\0是单独用memset处理的
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;//声明定义一个sdshdr结构体指针

sh = zmalloc(sizeof(struct sdshdr)+initlen+1);//开辟sdshdr结构体类型大小(8字节)+initlen+1(存放\0)字节。 头部赋给指针sh
#ifdef SDS_ABORT_ON_OOM
if (sh == NULL) sdsOomAbort();
#else
if (sh == NULL) return NULL;
#endif
sh->len = initlen;//把initlen数值赋给sh->len
sh->free = 0;//sh->free暂时初始化为0
if (initlen) {
if (init) memcpy(sh->buf, init, initlen);//如果init不为空 则 将init指针开始的initlen长度内存空间中的内容复制给sh->buf同样长度
else memset(sh->buf,0,initlen);//如果init为空 则 将sh->开始的initlen长度的空间的内容赋为0
}
sh->buf[initlen] = '\0'; //无论如何,buf的initlen下标处(也就是不包含\0的字符串的尾部的后一位)赋值为\0
return (char*)sh->buf;//返回一个指向buf的sds指针
}
//将字符串传入,生成对应的sds,适用于直接传一个常规的字符串,只写一个参数即可
sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init); //字符串为空则initlen=0,不为空则initlen等于不包含\0的字符串长度
return sdsnewlen(init, initlen);//调用sdsnewlen创建sds结构体
}
int main()
{
sds sdsp = sdsnew("yhping");
}

sds函数

创建

sdsnew/sdsnewlen

其中sdsnew调用了sdsnewlen

sdsempty

创建柔性数组内只含有'\0'sdshdr

sdsdup

创建sdshdr的副本

属性

sdslen返回已使用空间字节数即len;sdsavail返回未使用空间字节数即free。

释放

sdsfree

对应于创建sdshdr的sdsnew,即释放sdshdr

sdsclear

惰性释放,只删除字符串内容,更新len属性和free属性,结构保留。

1
2
3
4
5
6
7
void sdsclear(sds s)
{
struct sdshdr* sh = (struct sdshdr*)(s-sizeof(struct sdshdr));
sh->free += sh->len;
sh->len=0;
sh->buf[0]='\0';//不显式清零字符串内存空间内容,而是在逻辑上使字符串buf[0]=\0标志,使之无法访问。
}

扩展

sdsgrowzero

将柔性数组长度扩展到指定长度,多余空间用’\0’填充。

sdscatlen

可变参数

1
2
3
4
5
6
7
int a=10,b=20;
char buff[20];

printf("a=%d b=%d\n",a,b);//stdout a=10 b=20
sprintf(buff,"a=%d b=%d \n",a,b);
//把数据放到了buff中
sprintf_s(buff,20,"a=%d b=%d \n",a,b);
1
2
3
4
int a = 10,b = 20;
char buff[20];
int len = sprintf(buff,"a = %d b = %d \n",a,b);//返回字符串的长度15,格式化转化后存放的是"a = 10 b = 20 \n\0",但算长度时不包含\0长度
sprintf(buff,20,"a = %d b = %d \n",a,b);//相比于不带_s的sprintf更安全,因为限制了长度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void funa(int a,char cx,int b,char cy)
{
printf("%p => %d \n",&a,a);
printf("%p => %c \n",&cx,cx);
printf("%p => %d \n",&b,b);
printf("%p => %d \n",&cy,cy);
}
void funb(int a,float ft,double dx,char c)
{
printf("%p => %d \n",&a,a);
printf("%p => %f \n",&ft,ft);
printf("%p => %f \n",&dx,dx);
printf("%p => %c \n",&c,c);
}
void fun(int num, ...)//fun(3,'a',12,'b');
{
int* p = &num;
int* p1 = p+1;
printf("%d\n",*p1);
int* p2 = p1+1;
printf("%c\n",*p2);
int* p3 = p2+1;
printf("%d\n",*p3);
int* p4 = p3+1;
printf("%c\n",*p4);
}
int main()
{
funa(12,'a',23,'b');
funb(12,12.25,25.50,'b');//80-84-88-差了8个字节-90
//fun(0);
//fun(1,23);
//fun(2,23,34);
fun(3,'a',12,'b');
return 0;
}
1
2
3
4
5
6
7
8
void fun(const char* fmt, ...)
{

}
int main()
{
fun("%c %d %c \n",'a',12,'b');
}

image-20210721204739876

1
2
3
4
5
6
7
8
9
10
11
#include<stdarg.h>
void fun(const char* fmt, ...)
{
va_list ap = nullptr;
va_start(ap,fmt);
va_start(ap,char);
va_start(ap,int);
va_start(ap,char);

va_end(ap);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include<stdarg.h>
void fun(const char* fmt, ...)
{
va_list ap = nullptr;//typedef char* va_list
char* p = nullptr, * sval = nullptr;
int ival = 0;
double dval = 0;
va_start(ap,fmt);
for(p=fmt;*p!='\0';++p)
{
if(*p!='%')
{
switch(*++p)
{
case 'd':
ival = va_arg(ap,int);
printf("%d",ival);
break;
case 'f':
fval = va_arg(ap,float);
printf("%f",dval);
break;
case 's':
sval = va_arg(ap,char*);
printf("%s",sval);
break;
default:
putchar(*p);
break;
}
}
}
va_end(ap);
}
int main()
{
fun("a = %d\nft = %f\nstr = %s\n",12,12.25f,"hello tulun");
}
//#define va_start __crt_va_start
//#define va_arg __crt_va_arg
//#define va_end __crt_va_end
//#define va_copy(destination, source) ((destination) = (source))
//#define __crt_va_start(ap, x) __crt_va_start_a(ap, x)
//#define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
//#define __crt_va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
//#define __crt_va_end(ap) ((void)(ap = (va_list)0))