1:整型可以直接转成一个指针来用
#include <stdio.h>
#include <stdint.h>
void main()
{
char t = 32;
char* tp = &t;
printf("%d\n",t);
printf("%d\n",(void*)t);
printf("%p\n",(void*)t);
printf("%p\n",(void*)&t);
printf("%d\n",tp);
printf("%p\n",tp);
}
/**
输出..........
F:\WWW\CTest\smallTrain>main
32
32
0000000000000020
000000000061FE10
6422032
000000000061FE10
*/
ps: 整数类型比如unsigned char,unsigned short,unsigned int,unsigned long,因为地址也是一个16进制的数。另外通过typedef定义的整型也适用于该规则,比如int32_t,int64_t,size_t等
2:不同类型整数之间可以直接赋值,编译器不会校验
void main()
{
int t = 32768;
short st = t;
long long lt = t;
printf("%ld\n",t);
printf("%ld\n",st);
printf("%lld\n",lt);
printf("%d\n",sizeof(int));
printf("%d\n",sizeof(int32_t)); // typedef int int32_t;
printf("%d\n",sizeof(long));
printf("%d\n",sizeof(int64_t));//typedef long long int64_t
}
/**
F:\WWW\CTest\smallTrain>main
32768
-32768
32768
4
4
4
8
*/
*所以在特殊情况下,我们是可以直接把整数当成指针来用,比如int p=52647 **
ps: 低位数整型(short)赋值给高位数整型(int、long)是安全转换的,当反过来操作的时候就要注意数值溢出的问题了,以下为int赋值给short的溢出转换过程:
整型int占4个字节,变量int t = 32768,在内存中的存储情况如下:
00000000 00000000 10000000 00000000
整型short占2字节,当执行变量short st = t 时,直接截取t的后两个字节,可得到变量st 在内存中的存储情况如下:
10000000 00000000
因为负数以补码的形式存储,所以结果输出-32768。
3:循环里的局部变量地址不变
循环包含for、while、do…while
void main()
{
for (int i = 0; i < 4; i++) {
int p = 0;
static int q = 0;
printf("for:p %p,%d;q %p,%d\n", &p, ++p, &q,++q);
}
}
/**
F:\WWW\CTest\smallTrain>a
for:p 000000000061FE18,1;q 0000000000407030,1
for:p 000000000061FE18,1;q 0000000000407030,2
for:p 000000000061FE18,1;q 0000000000407030,3
for:p 000000000061FE18,1;q 0000000000407030,4
*/
ps: 可以看到局部变量p的地址是不变的,但是值却一直刷新。如果需要用到该变量的地址,要慎重。
出现这种现象的原因:局部变量是放在栈上的,循环结束一次就释放掉p的地址ADDR,下次循环再次申请相同类型变量就又重复使用该地址ADDR,编译器的一种优化策略。
所以对于该程序而言,局部变量p仍然是一个“新鲜”或“新”的变量。
4:‘\0’ ,‘0’, “0” ,0的区别
-
’\0’ 与 0的区别
‘\0’ 与 0 本质上其实是一样的,一个是ASCII码,一个是该ASCII码对应的字符。字符串结束符 ‘\0’ 的ASCII 值正好是 0。所以用 0 判断和用 ‘\0’ 判断,结果一样(在内存中存放一样)。
‘\0’ 就表示将字符 ’0’ 转义,系统遇到 ’\0’ 时输出一个空格,从而表示ASCII码为0的字符(字符串结束符),而数值 0 和这个是一个意思。 -
‘0’ 与 ‘\0’的区别
打开ASCII表,可以看到 字符’\0’ 对应的十进制是 0,字符’0‘对应的十进制是48 -
‘0’ 与 0的区别
这个就比较明显了,前者是字符常量,后者是整型常量 -
‘0’ 与 "0"的区别
这个就比较明显了,前者是字符常量,后者是字符串常量
5:数组初始化
void main()
{
char a[10] = {'c','p',0,'s',' ','v'};
printf("%p\n", &a);
printf("%s\n", a);
for(int i=0; i<10; i++){
printf("%c ==%d== %p\n", a[i],i,&a[i]);
}
}
/**
F:\WWW\CTest\smallTrain>main
000000000061FE12
cp //FIXME 这里只输出cp,是因为c里面认为 ‘\0’ 为字符串结尾
c ==0== 000000000061FE12
p ==1== 000000000061FE13
==2== 000000000061FE14
s ==3== 000000000061FE15
==4== 000000000061FE16
v ==5== 000000000061FE17
==6== 000000000061FE18
==7== 000000000061FE19
==8== 000000000061FE1A
==9== 000000000061FE1B
*/
从示例中可以得出:
1):数组的地址即是其第一个元素的地址;
2):char a[10] ={’a‘}并不表示所有元素都初始化为字符’a‘,仅仅是第一个元素初始化为字符’a‘,其他元素默认被填充为字符char的零值 ‘\0’,同理 int b[8]={1,2,3,4};等价于int b[8]={1,2,3,4,0,0,0,0};即如果初始化时指定的的元素个数比数组大小少,剩下的元素都回被初始化为数组类型的零值
3):字符数组可以方便地采用字符串直接初始化,比如 char exp[]=“abcdefghijklmn”;
5:字符串以'\0'结尾
字符串结尾为什么要以\0结尾?
- 在C语言里面并没有专门的字符串常量,而是以字符数组来表示,但是字符数组中的内容,直接显示,则可能出现乱码,因为编译器解释字符串时,从数组的内容中,依次显示字符,并不知道字符串到哪里结束。为了正确表示字符串的结束,使用’\0’空字符作为结束符,代表字符串的结束( ’\0‘是空字符,其ASCII码值为0,因为字符串中没有该字符,所以可以用该字符作为字符串的结束标志)。当然这只是一种大家约定俗成的默认处理方式而已。
- 事实上,很多机制可以不必以空字符作为结束符的。比如可以通过字符串长度来确定字符串的结束位置等等。而C语言就规定了必须以 ’\0‘做为字符串的结尾标识。因此,编译器从数组的内存开始一直往后找,一直找到 ’\0‘为止。也就是说,编译器其实会超过数组的长度找的,除非在数组内存范围内出现了 ’\0‘ 。如果你忘记了给字符数组的字符末尾添加 ’\0‘,因此就经常看到“烫烫烫烫”这种字样。出现这个“烫烫烫烫”的原因就是编译器预先将未使用的这些内存填充了,用于检测越界的情况等。 而填充的是0xcc字节,这也是你查看变量内存时,发现变量内存其他未使用的内存为什么是cc的原因。 而STL、MFC等工具库的字符串类,基本对字符数组封装了,他们有的不通过末尾的 ’\0‘来检测,而是使用字符串长度来记录字符串的字符个数。最终也有对应的机制,让显示字符串时在末尾 ’\0‘,只不过这些都不用程序员操心了。
- 说这么多就是告诉你,字符串结尾用 ’\0‘只是一个默认的机制,算是一个硬性规定。规定好后,编译器就好理解和执行了。而不表示所有字符串类型都用这种机制。比如win32的各种显示消息的函数,MessageBox等。这个可以自己去研究一下。
void main()
{
char a[5]="uvxyz";
printf("a:%s\n", a);
for (int i = 0;i<5;i++)
{
printf("i:%d,v:%c\n", i,a[i]);
}
}
/**
F:\WWW\CTest\smallTrain>a
a:uvxyzp
i:0,v:u
i:1,v:v
i:2,v:x
i:3,v:y
i:4,v:z
*/
可以明显看到如果声明的数组大小和字符个数一样,无法被自动填充’\0’,在输出的时候就会出现输出不存在字符的异常情况。总的来说 c,c++等语言以’\0’作为字符串结尾,只是一种确定字符串结束的方式而已。。如果一个字符串中没有’\0’这个结束字符,那么有些函数(比如printf)将不能确定字符串的结束位置在哪儿,从而引起一些不必要的错误,如果for循环访问该数组并不会有任何异常的。
6:数组名(一维)与指针的关系
注意:数组在内存中的存储就是一段连续的内存单元(线性表),因此在c,c++里 数组名就是该数组第一个元素的内存地址,这样通过下标索引就可以查询所有的数组元素。
void main()
{
char a[4]="uvx";
char* b;
b=a;
printf("a:%s\n", a);
printf("b:%s\n", b);
int i;
for (i=0;i<4;i++){
printf("[a] i:%d,v1:%c,v2:%c\n",i, a[i],*(a+i)); //*(a+1)并不是增加一个地址单元(在64位机器上是8个字节), 而是加一个内存单元, 具体的长度由指针类型决定
}
for (i=0;i<4;i++){
printf("[b] i:%d,v1:%c,v2:%c\n",i, b[i],*(b+i));
}
}
/**
F:\WWW\CTest\smallTrain>main
a:uvx
b:uvx
[a] i:0,v1:u,v2:u
[a] i:1,v1:v,v2:v
[a] i:2,v1:x,v2:x
[a] i:3,v1:,v2:
[b] i:0,v1:u,v2:u
[b] i:1,v1:v,v2:v
[b] i:2,v1:x,v2:x
[b] i:3,v1:,v2:
*/
从上面可以看出,指针可以以数组的形式使用,即通过b[i]这种形式访问;数组可以通过指针的形式访问,即*(a+i)的形式,原因何在呢?
原因是因为:数组是由有限长度的连续的内存单元组成的,a[i] 与*(a+i)是等价的,在编译时编译器遇上了a[i],会被编译成:*(a+i),下面的例子可能证明这一点。
void testArr(char* x)
{
printf("x szie:%d\n", sizeof(x));
}
void main()
{
char a[4]={'a','s','d'};
printf("a szie:%d\n", sizeof(a));
testArr(a);
}
/**
F:\WWW\CTest\smallTrain>main
a szie:4
b szie:8
*/
从上面的代码可以看出a的长度就是数组的长度4字节,而b的长度是指针的长度8字节(64位机器)。
因此数组的传参有以下三种形式:
//1
void func(int param[4],int size)
{
}
//2
void func(int param[],int size)
{
}
//3
void func(int* param,int size)
{
}
FIXME: 数组是比指针更高一层级的定义, 数组作为形参, 将退化为指针,所以在传参时要把数组长度作为参数传过去,一旦数组退化为指针就无法求数组长度了。
所以有时候在声明结构体指针时会用结构体数组代替,如下:
typedef struct
{
char* name;
int age;
}Person;
#define INIT_PERSON {0}
void testArr(Person* person)
{
printf("name:%s,age:%d\n", person->name,person->age);
}
void main()
{
Person persons[]={INIT_PERSON};
testArr(persons);
}
/**
F:\WWW\CTest\smallTrain>main
name:(null),age:0
*/
7:结构体
1:初始化
typedef struct
{
char *name;
//char name2[30];
int age;
}Person;
void main()
{
Person person={"xgg",20};//正常初始化
printf("name:%s,age:%d\n", person.name,person.age);
Person person1={0};//初始化为0值
printf("name:%s,age:%d\n", person1.name,person1.age);
}
/**
F:\WWW\CTest\smallTrain>main
name:xgg,age:20
name:(null),age:0
*/
2:结构体释放
typedef struct
{
char *name;
//char name2[30];
int age;
}Person;
void freePerson(Person* person)
{
if(person==NULL)
{
return;
}
if(person->name!=NULL){
free(person->name);
person->name=NULL;
}
free(person);
}
void main()
{
char* name="xgkk";
printf("sizeof(Person):%d,strlen(name):%d\n", sizeof(Person),strlen(name));
Person* person=(Person*)malloc(sizeof(Person));
person->name=(char*)malloc(strlen(name));
strcpy(person->name,name);
person->age=20;
printf("name:%s,age:%d\n", person->name,person->age);
//释放
freePerson(person);
printf("name:%s,age:%d\n", person->name,person->age);
person=NULL;
}
/**
F:\WWW\CTest\smallTrain>main
sizeof(Person):16,strlen(name):4
name:xgkk,age:20
name:P?age:10944848
*/
结构体释放不仅钥匙房结构体变量本身,还要是放结构体内部成员,
另外可以看到释放结构体变量person后,还可以访问person,只不过打印内容是错误的,这是因为free函数只是将指针指向的内存归还操作系统了,但是并不改变person的值 。
8:free后的指针还可以访问
void main()
{
char* name=(char*)malloc(5);
strcpy(name,"abcd");
printf("name:%s\n", name);
free(name);
printf("name:%s\n", name);
name=NULL;
}
/**
F:\WWW\CTest\smallTrain>main
name:abcd
name:`r
*/
关于free之后,指针的值为何没有改变,仍然还是这个原先堆空间的这个地址,并且可以访问,原因在于free函数仅仅是将malloc申请的内存释放回去,所谓的释放也就是告诉编译器(操作系统),这块内存已经使用完毕,可以收回了。但指针所指向的值并不会发生改变。就可以比方说,你租了一套房子,到期后,房子收回归还房东,而此时你可能还拿着房子的钥匙,这个时候你虽然可以继续访问这个房子(内存),但已经不属于你,是非法的。也可能有新的租客入驻更改房子的内置,也可能还是这个样子。取决于不同的房东(编译器)和租客(内容),这也是为什么要求在释放指针变量后要赋值为NULL,可以防止误访问。
这就是free释放内存后,指针内地址仍然存在,但有时还可以访问,有时候访问输出乱码或输出其他值的原因。
9:函数返回数组的方法
- 由调用方传入数组指针:此种方法不需要函数返回地址,而是在调用时直接 传入数组地址,委托被调用方进行操作,由于此局部变量属于调用方本身,故即便被调用方结束内存释放,也不会被影响到该数组。
int testArray(int arr[], int num)
{
for (int i = 0; i < num; i++)
{
arr[i] = i;
}
}
void main()
{
int arr[5];
testArray(arr,5);
for (int i = 0; i < 5; i++)
{
printf("arr[%d]:%d\n", i, arr[i]);
}
}
/**
F:\WWW\CTest\smallTrain>main
arr[0]:0
arr[1]:1
arr[2]:2
arr[3]:3
arr[4]:4
*/
- 直接使用全局变量或局部静态变量:这种方法最方便,但此方法打破了函数间的通信及封装的思想,所以不推荐使用,不在今天讨论范围之内
- 通过堆区动态开辟内存解决:C语言中,我们通常用malloc来在堆区动态开辟内存,利用堆区“现用现开辟,用完手动收回”特点,实现灵活管理。是实际开发中的常用办法,也是我们今天的主要内容。
1:定义一个结构体来返回数组内容和长度
typedef struct {
char** value;
int len;
} TestArray;
TestArray* return_array()
{
TestArray* arr = (TestArray*)malloc(sizeof(TestArray));
arr->len=5;
arr->value=(char**)malloc(sizeof(char*)*arr->len);;
char* name = "abcdefghijk";
for(int i =0; i<arr->len; i++)
{
//char* value = (char*)calloc(i+2,sizeof(char));
char* value = (char*)malloc((i+2)*sizeof(char));
memcpy(value, name, i+1);
printf("1 value[%d]:%s,len:%d\n",i,value,strlen(value));
value[i+1]='\0';
printf("2 value[%d]:%s,len:%d\n",i,value,strlen(value));
arr->value[i] = value;
}
return arr;
}
void main()
{
TestArray* arr = return_array();
for (int i = 0; i < arr->len; i++)
{
printf("arr[%d]:%s\n", i, arr->value[i]);
free(arr->value[i]);
arr->value[i] = NULL;
}
free(arr);//..... 释放
arr=NULL;
}
/**
F:\WWW\CTest\smallTrain>main
1 value[0]:abp,len:3
2 value[0]:a,len:1
1 value[1]:abp,len:3
2 value[1]:ab,len:2
1 value[2]:abc,len:3
2 value[2]:abc,len:3
1 value[3]:abcd,len:4
2 value[3]:abcd,len:4
1 value[4]:abcde,len:5
2 value[4]:abcde,len:5
arr[0]:a
arr[1]:ab
arr[2]:abc
arr[3]:abcd
arr[4]:abcde
*/
2:直接申请区一块内存
char* return_array1()
{
char* res = (char*)malloc(6*sizeof(char));//此时res=[]
strcpy(res, "abcd");//注意 "abcd" = "abcd\0"
res[5] = '\0';
return res;
}
void main()
{
char* str = return_array1();
printf("res:%s,len:%d\n",str,strlen(str));
free(str);
str=NULL;
}
/**
F:\WWW\CTest\smallTrain>main
res:abcd,len:4
*/
9:char*和char[]的区别
下面两篇文章很清晰了,直接看吧
char *c和char c[]区别
C 函数参数 char **s与char *s[]