内存管理:
| 局部变量 | 静态局部变量 | 全局变量 | 静态全局变量 | 作用域 | 在定义变量的{}之内有效 | 在定义变量的{}之内有效 | 整个工程,所有文件 | 当前文件 | 生命周期 | 程序运行至变量定义处开辟空间,所在的函数结束之后释放空间 | 执行main函数之前就已经开辟空间,程序结束之后才释放空间 | 执行main函数之前就已经开辟空间,程序结束之后才释放空间 | 执行main函数之前就已经开辟空间,程序结束之后才释放空间 | 未初始化的值 | 随机 | 0 | 0 | 0 |
注意:在.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?保存的地址一定是定义过的(向系统申请过的)。
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 * )
万能指针可以保存任意的地址,但是如果想通过万能指针操作这个指针所指向的空间的内容的时候,就要通过强制类型转换将这个万能指针转换一下,如下所示。
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
另外:
①?void?b;? //错误,不可以定义void类型的变量,因为编译器不知道给变量分配多大的空间。
但是可以定义void *类型,因为指针都是4个字节。
②?? ? printf("%d\n", *(int*)p); ? ? ?//强制类型转换:由 “ * 地址 ”转换为“ *((int *)地址) ”
这个语句中,p?就是a的地址,但是p的类型是void *类型。为了取到4个字节的a的值,先使用强制类型转换将地址类型转换成4个字节的int *类型,然后取int *类型的地址里的内容,即a的值。
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)数组作为函数的参数
数组作为函数的形参会退化为指针。?
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;
}
(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[] = "helloworld"; //定义了一个字符数组,内容为helloworld
char* p = a; //定义了一个指针用来保存数组首元素的地址
p = "abcd"; //字符串常量存在文字常量区,“”在使用时,取的是字符串首元素的地址
*p = 'm'; //err,文字常量区的内容是不可以改变的
system("pause");
return 0;
}
18.?字符指针作为形参
char* my_strcat(char* src, char* dst)
{
int n = strlen(src);
int i = 0;
while (*(dst + i) != 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修饰的指针变量
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, cahr* 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]。
|