C语言的一些小的知识点

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的区别

  1. ’\0’ 与 0的区别
    ‘\0’ 与 0 本质上其实是一样的,一个是ASCII码,一个是该ASCII码对应的字符。字符串结束符 ‘\0’ 的ASCII 值正好是 0。所以用 0 判断和用 ‘\0’ 判断,结果一样(在内存中存放一样)。
    ‘\0’ 就表示将字符 ’0’ 转义,系统遇到 ’\0’ 时输出一个空格,从而表示ASCII码为0的字符(字符串结束符),而数值 0 和这个是一个意思。

  2. ‘0’ 与 ‘\0’的区别
    打开ASCII表,可以看到 字符’\0’ 对应的十进制是 0,字符’0‘对应的十进制是48

  3. ‘0’ 与 0的区别
    这个就比较明显了,前者是字符常量,后者是整型常量

  4. ‘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[]

9:static和const

static和const
static和const区别

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值