内存管理:
局部变量 | 静态局部变量 | 全局变量 | 静态全局变量 | |
作用域 | 在定义变量的{}之内有效 | 在定义变量的{}之内有效 | 整个工程,所有文件 | 当前文件 |
生命周期 | 程序运行至变量定义处开辟空间,所在的函数结束之后释放空间 | 执行main函数之前就已经开辟空间,程序结束之后才释放空间 | 执行main函数之前就已经开辟空间,程序结束之后才释放空间 | 执行main函数之前就已经开辟空间,程序结束之后才释放空间 |
未初始化的值 | 随机 | 0 | 0 | 0 |
在C语言中,内存分配方式有以下三种方式:
(1)从静态存储区域分配:
它是由编译器自动分配和释放的,即内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,直到整个程序运行结束时才被释放,如全局变量与static变量。
(2)在栈上分配:
同样是由编译器自动分配和释放的,即在执行函数时,函数内部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元将被自动释放。
(3)从堆上分配:
也称为动态内存分配,手动进行的分配。
内存分配中的栈区主要用于分配局部变量空间,处于较高的地址,其栈地址是向下增长的;
而堆区则主要用于分配程序员申请的内存空间,堆地址是向上增长的。
堆和栈的区别:
堆 | 栈 | |
分配的碎片问题 | 频繁的分配和释放不同大小的堆空间会造成内存空间的不连续,从而造成大量碎片。 | 不存在内存碎片。 |
分配的效率 | 堆是由C/C++函数库提供的。操作系统有一个记录空闲内存地址的链表,当系统收到分配一块堆内存的申请时,会遍历该链表,寻找一个空间大于所申请空间的堆节点,然后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序。 | 栈是机器系统提供的数据结构,计算机会在底层对栈提供支持。例如:分配专门的寄存器存放栈的地址,压栈出栈都有专门的执行指令,因此栈的效率比较高。 |
申请的大小限制 | 由于操作系统是用链表来存储内存地址的,同时链表的遍历方向是由低地址向高地址。因此,堆内存的申请大小受限于计算机系统中有效的虚拟内存。 | 栈是一块连续的内存区域,栈顶的地址和最大容量一般由系统预先规定好,如果申请的空间超过栈的剩余空间,会提示溢出错误。因此,从栈获得的空间相对较小。 |
存储的内容 | 根据需要存储数据。 | 存放函数的参数与局部变量。 在函数调用时,第一个进栈的是调用处的下一条指令(主函数中)的地址,然后是函数的各个参数(因为栈是先进后出)。 参数是由右往左入栈的,最后是函数中的局部变量(注意:static变量是不入栈的!)。 出栈时,局部变量先出栈,然后是参数,最后栈顶指针指向最开始保存的地址,也就是主函数中的下一条指令。 |
最后介绍一下C语言中各类型变量的存储位置。
- 全局变量:从静态存储区域分配。在当前的.h文件中使用extern声明后,可在其他文件中使用。
- 全局静态变量:从静态存储区域分配。
- 局部变量:从栈上分配。作用域只在局部函数内。
- 局部静态变量:从静态存储区域分配。作用域只在定义它的函数内可见。
注意:在.h中,全局变量只声明不定义,定义只放在.c文件。
memset:
memcpy:
memcmp:
malloc:
free:
数组
1. 计算机的最小存储单位是字节Byte,共有8个bit构成,不管是32位还是64位CPU。
2. break:强行从循环体跳出,执行循环语句块后面的语句;
continue:从continue点返回循环体条件判断,继续进行下一次循环动作。
3. int *p:定义p指针变量,该变量用来指向一个地址,该地址存放的是一个int型数据。
4. 一维数组
eg:int a[5];
- a[0]: 第 0 个元素
- &a[0]: 第 0 个元素的地址
- 数组名 a :代表数组,也代表第0个元素的地址,等于&a[0]。所以说数组名是一个常量,是一个地址,不能被赋值。
- &a : 数组的地址,在数值上,&a[0],a,&a三者相等。
- &a[0]+1: 元素的地址加1,表示跨过一个元素
- a+1: a = &a[0],所以也表示跨过一个元素
- &a+1: 整个数组的地址加1,表示跨过一个数组
5. 二维数组
eg:int a[3][4]
- 定义了一个三行四列的数组,数组名为a,元素类型为整型。
- 二维数组a的元素是按行进行存放的。即先存完第0行,再存下一行。
- 一维数组的a[0]代表第0个元素,二维数组的a[0]代表第0行。
- a[0][0]: 第0行第0个元素
- &a[0][0]:第0行第0个元素的地址
- a[0]: 代表第0行这个一维数组的数组名,a[0] = &a[0][0]
- &a[0]: 第0行的地址
- a: 二维数组的数组名,代表二维数组,也代表首行地址,a = &a[0]
- &a: 二维数组的地址
- &a[0][0] + 1 :元素地址加 1,跨过一个元素
- a[0] + 1 : 元素地址加 1,跨过一个元素
- &a[0] + 1 : 行地址加 1,跨过一行
- a + 1: 行地址加 1,跨过一行
- &a + 1: 二维数组地址加 1,跨过整个数组
6. 字符数组
int a[10] //每个元素是int类型,所以这个是数值数组
char a[10] //每个元素是char类型,所以这个是字符数组
字符串就是字符数组中有 \0 字符的数组。
eg:
① 普通的字符数组:
char a[5] = {'a','b','c','d','e'};
② 字符数组中含有 \0 字符,它也是个字符串:
char a[5] = {'a','b','c','d','\0'};
③ 定义了一个字符数组,存的是 abcd\0:
char a[5] = "abcd";
④ 定义了一个字符数组,有100个元素:
char a[100] = "abcd";
⑤ 将数组的第0个元素填 \0,其他元素就是 \0:
char a[100] = "\0";
⑥ 将一个字符数组清0:
char a[100] = {0};
字符数组与字符串的区别:
- C语言中没有字符串这种数据类型,可以通过 char 的数组来替代;
- 字符串一定是一个 char 的数组,但 char 的数组未必是字符串;
- 数字0(和字符'\0'等价)结尾的 char 数组就是一个字符串,但如果 char 数组没有以数字0结尾,那么它就不是一个字符串,只是普通的字符数组。
⑦ 普通字符数组,如果用 %s 打印的话(%s 是指输出字符串格式,从字符数组的起始地址,即首元素地址开始打印,直至出现 \0 为止),没有 \0 结束符的话会出现乱码。
函数
1. 函数定义时,()里面的参数叫形参,因为这个形参只是形式上的参数,在定义函数时没有给形参开辟空间。形参只有在被调用时才会分配空间。
2. 调用函数时,()里面的参数叫做实参,实参的类型和形参的必须一致、个数必须相同。
3. 头文件中只声明,不定义;只在 .c 中定义。
4.防止头文件重复包含:
①
#ifndef 宏(宏的名字最好和文件相同,大写)
#define 宏
#endif
② #pragma once
5. 静态函数:函数定义时加上static修饰的函数。静态函数只能被当前文件函数调用。
指针
5. 定义指针: 用 *p 替换掉定义的变量。
eg:int a,把 a 用 *p 代替,得到 int *p。
6. int *p:与 * 结合代表这是一个指针变量;
p 是变量,p 的类型是:将变量p本身拖黑,剩下的类型就是指针变量的类型,即 int *;
指针变量 p 用来保存什么类型的数据的地址:将指针变量 p 和最近的一个 * 一起抹黑,剩下的就是保存的数据的类型,eg:int **p,保存的是 int * 数据类型。
7. 指针的宽度 = sizeof(将指针变量和指针变量最近的 * 拖黑,剩下的长度)
宽度也叫步长,即指针加 1 跨过多少个字节。
指针加 1 ,则跨过一个步长。
eg:
char *p | 1 |
short *p | 2 |
int *p | 4 |
int **p | sizeof(int *) = 4 |
8. 野指针
就是没有初始化的指针,指针的指向是随机的,不可以操作野指针。
指针 p 保存的地址一定是定义过的(向系统申请过的)。
出现野指针的几种情况:
(1)声明未初始化的指针
int *p;
printf("%d\n", *p); //err
(2)malloc 后 free的指针
如下,第一次printf可以输出100,第二次就错误了。因此,我们一般在free(p)后加一句 p=NULL;
int* p = malloc(sizeof(int));
*p = 100;
printf("%d\n",*p);
free(p);
printf("%d\n", *p);
(3)指针变量超出了作用域
如下,变量 a 在被调函数结束执行后就被释放了,所以p2接收到的已经不是我们需要的值了。
int* app()
{
int a = 10;
int* p = &a;
return p;
}
int main()
{
int* p2 = app();
printf("p2 = %d\n", *p2);
system("pause");
return 0;
}
9. 空指针,就是将指针的值赋值为0,即 null。eg:int *p = NULL。
空指针的作用:如果使用完指针,将指针赋值为null,在使用时判断一下指针是否为null,就知道指针有没有被使用。
看下面的代码:
int main()
{
int *p = NULL; //给指针的值赋值为0,即null
*p = 200; //err,因为 p 保存了0x0000的地址,这个地址是不可以使用的
printf("%d\n", *p);
system("pause");
return 0;
}
10. 万能指针( void * )
(1)万能指针可以保存任意的地址。
int main()
{
int a = 10;
short b = 20;
void* p = (void*)&a; //万能指针可以保存任意的地址,需要先把指向的地址转换为void *类型
void* q = (void*)&b;
//printf("%d\n", *p); //err,p是void *,不知道取几个字节的大小
printf("%d\n", *(int*)p); //强制类型转换:由 “ * 地址 ”转换为“ *((int *)地址) ”
printf("%d\n", *(short*)q);
system("pause");
return 0;
}
输出为:
10
20
(2)如果想通过万能指针操作这个指针所指向的空间的内容的时候,就要通过强制类型转换将这个万能指针转换一下,例如:
printf("%d\n", *(int*)p); //强制类型转换:由 “ * 地址 ”转换为“ *((int *)地址) ”
这个语句中,p 就是a的地址,但是p的类型是void *类型。为了取到4个字节的a的值,先使用强制类型转换将地址类型转换成4个字节的int *类型,然后取int *类型的地址里的内容,即a的值。
(3)
void b; //错误,不可以定义void类型的变量,因为编译器不知道给变量分配多大的空间。
但是可以定义void *类型,因为指针都是4个字节。
(4)不同类型的指针之间,需要强制转换才不会报警告。但是,万能指针不通过强制类型转换,就可以转为任意类型的指针。
int main()
{
void* p = NULL;
int* pInt = NULL;
char* pChar = NULL;
pChar = (char*)pInt; //需强制类型转换
pChar = p; //不需要转换
system("pause");
return 0;
}
11. const修饰的指针
看const修饰的是 * 还是 p。
(1)修饰 *,不能通过 *p 修改p所指向的空间的内容。
const int *p = &a;
*p = 100; //err
(2)修饰变量 p,则 p 保存的地址不可以修改。
int *const p = &a;
p = &b; //err
(3)p 本身的指向不能改变,且不能通过 *p 修改p指向空间的内容。
const int *const p = &a;
12. 通过指针操作数组元素
指针加1,跨过一个步长。
int *p; 步长 = sizeof(int)
int main(int argc, char const *argv[])
{
int a[10] = {0};
// a 表示数组名、首元素地址
int *p = a; // 指针 p 保存的是首元素的地址
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
*(p + i) = i; //指针加1,则跨过一个步长
}
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
printf("%d ", a[i]);
}
system("pause");
return 0;
}
13. 指针运算
两指针相加没有意义;两指针相减(类型一致),得到的是中间跨过多少个元素,看下边的代码:
int main(int argc, char const *argv[])
{
int a[5] = { 1,2,3,4,5 };
int* p = a;
int* q = (int *)(&a + 1) - 1;
printf("%d\n", *(p + 3));
printf("%d\n", q - p);
system("pause");
return 0;
}
输出为:
4
4
14. 如下代码所示,四个 printf 表示的含义一样。即,[ ] == *() 。
int main(int argc, char const *argv[])
{
int a[5] = { 1,2,3,4,5 };
int* p = a;
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
printf("%d ", a[i]);
printf("%d ", *(p + i));
printf("%d ", p[i]);
printf("%d ", *(a + i));
}
system("pause");
return 0;
}
15. 指针数组
整型数组:数组的每一个元素都是整型
指针数组:数组的每一个元素都是一个指针
eg:int *arr[3] = {&a, &b, &c};
16. 指针与函数
(1)指针作为函数的形参,可以改变实参的值。
eg:交换两个数
void swap(int *x, int *y)
{
int k = *x;
*x = *y;
*y = k;
printf("x = %d y = %d\n", *x, *y);
}
int main(int argc, char const *argv[])
{
int a = 10;
int b = 20;
swap(&a, &b);
printf("a = %d b = %d\n", a, b);
system("pause");
return 0;
}
(2)数组作为函数的参数
- 数组作为函数的形参会退化为指针。
- 数组作为函数的参数时,必须再用另一个参数来传入数组的大小。
eg1:
void print_arr(int *b, int len)
{
int n = sizeof(b) / sizeof(b[0]); //b是int*类型,b[0]是int类型,则n=4/4=1
printf("n = %d\n", n);
for (int i = 0; i < len; i++)
{
printf("%d ", b[i]);
}
printf("\n");
}
int main()
{
int a[5] = {1,2,3,4,5};
print_arr(a, sizeof(a) / sizeof(a[0])); //这里传的是首元素的地址,所以函数里也应该定义一个int*类型来接。
system("pause");
return 0;
}
eg2:找出数字key在数组a中的位置。
int search(int key, int a[], int length)
{
int ret = -1;
for (int i = 0; i < length; i++)
{
if (a[i] == key)
{
ret = i;
break;
}
}
return ret;
}
int main()
{
int a[] = {1,2,3,4,52,64,18,9,0};
int x;
int loc;
printf("请输入一个数字:\n");
scanf("%d", &x);
loc = search(x, a, sizeof(a)/sizeof(a[0]));
if (loc != -1)
{
printf("数字%d在数组中位于第%d个位置上。\n", x, loc);
}
else
{
printf("不存在\n");
}
return 0;
}
(3)指针作为函数的返回值
局部变量的空间在函数结束后会被释放,所以这里把num定义为全局变量,就可以返回其地址。
int num = 0;
int *get_num()
{
srand(time(NULL));
num = rand();
return # //局部变量的空间在函数结束后会被释放,所以这里把num定义为全局变量,就可以返回其地址。
}
int main()
{
int *p = get_num();
printf("%d\n", *p);
system("pause");
return 0;
}
习题:
char arr[] = "hello world";
char *p = arr;
int i = 0;
for(; i<sizeof(arr)/sizeof(char); ++i)
{
printf("%c", p[i]);
}
输出为:hello world
for循环中 ++i 和 i++的区别:结果都是一样的,都要等代码块执行完毕后才能执行语句3。
17.指针与字符数组
int main()
{
char a[] = "hello";
char* p = a;
printf("%s\n", p); //%s:打印一个字符串,要的是首个字符的地址
printf("%s\n", p + 2);
printf("%c\n", *p);
printf("%c\n", *(p + 2));
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(p));
printf("%d\n", strlen(a));
printf("%d\n", strlen(p));
*p = 'm';
printf("%s\n", p);
p++;
*p = 'n';
printf("%s\n", p);
}
输出为:
hello
llo
h
l
6
4
5
5
mello
nllo
17.1 字符串常量
字符串常量存放在文字常量区,文字常量区的内容不可以改变。
但是可以创建一个字符数组,并使用字符串字面量初始化它,这意味着字符串的内容被复制到了数组中。此时执行*p = 'm';
时,实际修改的是数组a的第一个元素,而不是修改原始的字符串字面量。
int main()
{
//定义了一个字符数组,字符数组的内容是helloworld。a[]是局部变量,存储在栈区
char a[] = "helloworld"; //此时已经在栈区开辟了空间,所以helloworld存在栈区里
char* p = a;
*p = 'm'; //这里可以更改p指向的地址的内容,因为p指向的字符串是在栈区
printf("%s\n", p); //输出为:melloworld
p = "abcd"; //"abcd"是字符串常量,存储在文字常量区,这里是把字符串常量的首元素地址赋值给了p
printf("%s\n", p); //输出为:abcd
*p = 'm'; //err,此时p指向的是文字常量区,文字常量区的内容是不能改变的
printf("%s\n", p);
system("pause");
return 0;
}
总结一下:
- 字符串字面量
"helloworld"
是不可修改的(通常)。- 字符数组
a
是可以修改的,并且使用字符串字面量来初始化它时,字符串的内容会被复制到数组a
中。- 指针
p
指向数组a
的首地址,因此*p
实际上指的是a[0]
,你可以修改它。
18. 字符指针作为形参
char* my_strcat(char* src, char* dst)
{
int n = strlen(src);
int i = 0;
while (*(dst + i) != 0) //0等价于'\0'
{
*(src + n + i) = *(dst + i);
//等价于src[n+i] = dst[i];
i++;
}
*(src + n + i) = 0; //字符串结尾加 \0
return src;
}
int main()
{
char str1[128] = "hello";
char str2[128] = "12345";
printf("%s\n", my_strcat(str1, str2)); //链式调用,上一个函数的返回值作为下一个函数的参数
system("pause");
return 0;
}
19. const修饰的指针变量
const char* p
char* const p
从左往右看,跳过类型,看修饰哪个字符:
如果是 *,说明指针指向的内存不能改变;
如果是指针变量,说明指针的指向不能改变,指针的值不能修改。
int main()
{
char buf[] = "hello";
char str[] = "acbg";
const char *p = buf;//const修饰指针,不能通过指针修改指针所指向的空间内容
//*p = 'b'; err 不能通过p指针修改那块空间的内容
char * const k = buf;//指针变量k初始化之后不能改变k指针变量本身的指向
// k = "world"; err
// k = str; err
system("pause");
return 0;
}
20. 字符指针数组
是一个数组,每一个元素是字符指针。
int main()
{
char* num[3] = { "haha","hehe","xixi" };
char** p = num;
//定义一个指针保存num首元素的地址,首元素num[0]是char*类型,则 char* 的地址是char**。
//因此 p 表示num[]中首元素的地址,即&num[0];
//*p 表示num[0]的值,即"haha"的地址;
// p+1 表示&num[1],则*(p+1)表示num[1]的值,即"hehe"的地址;
//*(*(p+1)+1)表示"hehe"中第一个e;
for (int i = 0; i < 3; i++)
{
printf("%s\n", p[i]);
}
printf("%c\n", *(*(p + 1) + 3)); // == *(p[1]+3) == p[1][3]
system("pause");
return 0;
}
输出为:
haha
hehe
xixi
e
下图即为上述代码所示。
21. 字符指针数组作为main函数的形参
int main(int argc, char* argv[ ])
argc是执行可执行程序时的参数个数;
argv是一个字符指针数组,保存的是参数(字符串)的首元素地址。
22. 习题
(1)
void sum(int *a)
{
a[0]=a[1];
}
main( )
{
int aa[10]={1,2,3,4,5,6,7,8,9,10},i=0;
for(i=2;i>=0;i--)
sum(&aa[i]);
printf("%d\n",aa[0]);
}
输出为:4。
解析:第一次调用sum()函数时,传入的是aa[2]的地址,那么sum()函数里的 a 就指向了aa[2],a[0]即等于aa[2],a[1]即等于aa[3]。
(2)
char s[10];
s = "abcd";
解析:错误。s 是数组名,是个常量,不能被赋值。
(3)
若有下面的变量定义,则以下语句合法的是:
int i=0, a[10]={0}, *p=NULL;
A. p=a+2 B. p=a[5] C. p=a[2]+2 D.p=&(i+2)
解析:A
A:合法。a代表首元素地址,a+2代表a[2]的地址;
B、C:等号左边p为int*类型,等号右边为int类型;
D:i+2是常量,对常量不能取地址。
(4)
下面程序的输出结果是:
int f(char* s)
{
char* p = s;
while (*p != '\0')
{
p++;
}
return(p - s);
}
int main()
{
printf("%d\n", f("HENAN"));
system("pause");
return 0;
}
解析:5
流程:主函数调用了f()函数,把字符串传了进去,则s就指向了首元素的地址;然后,p也指向了首元素的地址;while里判断如果没遇到字符串结束符,则p+1;直到最后即为求字符串的长度。
(5)
void fun(char s1[])
{
int i, j;
for (i = j = 0; *(s1 + i) != '\0'; i++)
{
if (*(s1 + i) < 'n') //*(s1+i)等价于s1[i]
{
*(s1 + j) = *(s1 + i);
j++;
}
}
*(s1 + j) = '\0';
}
int main()
{
char str[]="morning",*p;
p = str;
fun(p);
puts(p);
system("pause");
return 0;
}
解析:mig
① 数组作为函数的形参时,会退化为指针,即 char s1[] 等价于 char *s1,p指向首元素的地址,则s1也指向首元素的地址;
② 当 s1[i] < 'n' 时,则把 s1[i] 的值赋给 s1[j],然后j++,i++,继续循环。
(6)
下面程序的输出结果是:
int main()
{
char str[]="abc\0def\0gh";
char* p = str;
printf("%s\n",p+5);
printf("helloxx\0xxworld\n");
printf("\n------------\n");
printf("hello%sworld\n", "xx\0xx");
system("pause");
return 0;
}
输出为:
ef
helloxx
------------
helloxxworld
解析:
printf() 输出字符串时,遇到第一个 \0 即结束。
(7)
void fun(char* c, int d)
{
*c = *c + 1;
d = d + 1;
printf("%c,%c,", *c, d);
}
int main()
{
char a = 'A', b = 'a';
fun(&b, a);
printf("%c,%c\n", a, b);
system("pause");
return 0;
}
输出为:b,B,A,b
解析:传入地址的话,会改变值。
(8)
int a = 2;
int f(int* a)
{
return (*a)++;
}
int main()
{
int s = 0;
int a = 5;
s += f(&a);
s += f(&a);
printf("%d\n", s);
}
输出为:11
解析:
main() 里调用的f()函数,传入的是局部变量a的地址;
return(*a)++,先返回*a,即局部变量a的值,再执行++,即a+1。
同理,看下边这个题:
int main()
{
int a[5] = {1,3,5,7,9};
int *p = a;
printf("%d\n", (*p)++);
printf("%d\n", *(p++));
printf("%d\n", *p++);
printf("%d\n", *p);
}
输出为:1 2 3 5
解析: printf() 函数的计算是从右往左的。
第一个:和上边一样,先输出*p,即a[0]的值,然后再执行 *p+1,即a[0] = a[0]+1 = 2;
第二个:此时 p 还是指向a[0],当执行 *(p++) 时,从右往左执行,看到有 p++,还是先执行输出 *p,再进行++。这条语句执行完后输出为2,p指向a[1];
第三个:运算符优先顺序,*和++同等优先级,则根据printf()从右往左的执行顺序,*p++ = *(p++)。这条语句执行完后输出为3,p指向a[2];
第四个:输出a[2]。
(9)输出为:100
本题涉及的几个点:① 用memcpy(目标地址,源地址,长度)函数将a拷贝至buf中;② 用指针p指向数组buf的地址;③ 将char*类型的p强制转换为int *类型,然后再加*,用来表示完整的值a。
int main()
{
char buf[1024] = { 0 };
int a = 100;
memcpy(buf, &a, sizeof(int));
char* p = buf;
printf("%d\n", *(int*)p);
system("pause");
return 0;
}
(10)指针做函数参数的输入特性:在主调函数分配内存
包括:① 栈上分配内存;② 堆上分配内存。
void func(char* p)
{
strcpy(p, "hello world");
}
void test_1()
{
//在栈上分配内存
char buf[1024] = { 0 };
func(buf);
printf("%s\n", buf);
}
void printString(char* str)
{
printf("%s\n", str);
}
void test_2()
{
//在堆区分配内存
char* p = malloc(sizeof(char) * 64);
memset(p, 0, 64);
strcpy(p, "hello world");
//在被调函数中 输出helloworld
printString(p);
free(p);
p = NULL;
}
int main()
{
test_1();
test_2();
system("pause");
return 0;
}
输出为:
hello world
hello world
(11)指针做函数参数的输出特性:在被调函数分配内存,分配在堆区内存
void allocateSpace(char** p)
{
char* temp = malloc(sizeof(char) * 64);
memset(temp, 0, 64);
strcpy(temp, "hello world");
*p = temp;
}
void test_3()
{
char* p = NULL;
allocateSpace(&p);
printf("%s\n", p);
free(p);
p = NULL;
}
int main()
{
test_3();
system("pause");
return 0;
}
(12)
int main()
{
char str[] = "hello\012world";
printf("%s\n", str);
printf("sizeof str:%d\n", sizeof(str));
printf("strlen str:%d\n", strlen(str));
system("pause");
return 0;
}
\012 为八进制数字,转为十进制为10,对应ASCII码里的换行。
则输出为:
hello
world
sizeof str:12
strlen str:11
23. 实现字符串拷贝的三种方法:
如下代码,三种方式输出均为 hello world
void copyString01(char* des, char* src)
{
//利用下标方式拷贝
int len = strlen(src);
for (int i = 0; i < len; i++)
{
des[i] = src[i];
}
des[len] = '\0';
}
void copyString02(char* des, char* src)
{
//利用字符串指针
while (*src != "\0")
{
*des = *src;
des++;
src++;
}
*des = '\0';
}
void copyString03(char* des, char* src)
{
while(*des++ = *src++)
{ }
}
void test()
{
char* str = "hello world";
char buf[1024];
copyString01(buf, str);
//copyString02(buf, str);
//copyString03(buf, str);
printf("%s\n", buf);
}
int main()
{
test();
system("pause");
return 0;
}
24. 字符串反转
void reverString1(char* str)
{
int len = strlen(str);
int start = 0;
int end = len - 1;
while (start < end)
{
char tmp = str[start];
str[start] = str[end];
str[end] = tmp;
start++;
end--;
}
}
void reverString2(char* str)
{
int len = strlen(str);
char* start = str;
char* end = str + len - 1;
while (start < end)
{
char tmp = *start;
*start = *end;
*end = tmp;
start++;
end--;
}
}
void test()
{
char str[] = "abcd";
reverString1(str);
reverString2(str);
}
int main()
{
test();
system("pause");
return 0;
}
25. 移位运算符
左移:num<<n, 等于num乘以2的n次幂
右移:num>>n, 如果num非负,则num除以2的n次幂;如果为负,分机器。
void test_05()
{
int num1 = 20;
int num2 = 10;
printf("num1 <<= 3 = %d\n", num1 <<= 3);
printf("num2 >>= 1 = %d\n", num2 >>= 1);
}
int main()
{
test_05();
system("pause");
return 0;
}
输出为:
num1 <<= 3 = 160
num2 >>= 1 = 5
26. 指针越界
void test()
{
char buf[3] = "abc";
printf("buf:%s\n",buf);
}
输出为:
buf:abc烫烫?
郳曾?
27. 指针释放时容易出错的地方
指针进行叠加运算时,会不断改变指针的指向,此时释放原指针的话会出错。解决方法是:创建一个临时指针指向原指针,并操作这个临时指针,最后释放原指针的话就不会出错。
如果递增的是原指针p,实际上就丢失了对原始分配的内存块起始地址的引用。这意味着在循环结束后,p指向了内存中一个新的、未知的位置,而不是最初malloc分配的内存块的起始地址。
void test()
{
char* p = malloc(sizeof(char) * 64);
char* temp = p; //使用temp指针来遍历内存块
for (int i = 0; i < 3; i++)
{
*temp = i + 97; //修改temp指针当前指向的字符
printf("%c\n", *temp);
temp++;
}
if (p != NULL)
{
free(p); //使用p指针(原始地址)来释放内存
p = NULL;
}
}
int main()
{
test();
system("pause");
return 0;
}
输出为:
a
b
c