title: <<你必须知道的495个C语言问题>>
categories: C书籍
一. 声明与初始化
我该用哪种类型
如果你定义明确的溢出特征,不想把正负号牵扯在内的话
如果你希望操作二进制位和字节时避免符号扩展
符号扩展
是在二进制位前面用1补全或者用0补全
指针声明
char* p1,p2;
我这样声明有什么问题?
组后得到的数据类型是p1位char指针,而p2是char整形
声明语法的解释
基本类型 生成基本类型的东西
而我们的*
是一属于后者,于是不能出现在基本类型的符号里面
上面的例子改为
char *p1, *p2
类型定义typedef
defeine与typedef的区别确实是define只做一个字符串的替换..但是细微下来,好像又不是
typedef char *ptr1;
#define ptr2 char*;
ptr1 str1,str2;
ptr2 str3,str4;
我们定义了4个变量
str1,2,3都是指针变量,而str4却不是
原因
- 就是因为#define做了一个字符串的替换,并且因为类型声明的原因,str4不是指针
- tyedef真的可以把它后面的数据都定义为那一种数据
define也不是一无是处….defiene的作用很多.比如很多与define有关的命令
有关结构体和一般链表的声明
struct
{
int ID;
char* Name;
int arr[20];
}new_type;
这种声明只能用一次.然后这种声明就真的一无是处吗?????
用在typedef
就很不错的效果
用typedef去定义链表
错误的情况
typedef struct
{
int ID;
char *Name;
student Next;//只是一个链表指针
}* student;//我们呢这里已经定义了一个新的结构体指针类型.名字叫student
于是编译器报错
[Error] 'student' does not name a type
他说我们没有定义那个类型‘student’
哦???
好像是的
因为我们以前都是把(int,char,char *,i nt *…)这些已知的数据类型来重新封装一下
对于上面提到的链表相关的typedef里面的成员类型student Next;
就是一个我们之前没有的东西
那该怎么办
那就声明一个假的变量..就像你去图书馆..你可以人不在,,却可以拿上一本书去占位置
C0de
struct dqx;//事先准备好一个结构体标签
typedef struct dqx* Next_ptr;//也定义一个那样的结构体指针类型
struct dqx
{
int ID;
char *Name;
Next_ptr Next;//这里直接使用
};
//这种写法不建议使用..就很绕..你懂吗.....
这样写好些
typedef struct dqx
{
int ID;
char *Name;
struct dqx *Next;//只是一个链表指针
} *Student;
还有一种写法
struct dqx
{
int ID;
char* NAme;
struct dqx* next;
};
typedef struct dqx* student_ptr;
student_ptr ahead;
student_ptr behind;
这样定义链表指针会更加的方便
闭环链表的typedef
这样写编译器会报错
typedef struct
{
B_ptr b_pointer;
}* A_ptr;
typedef struct
{
A_ptr a_pointer;
}* B_ptr;
[Error] 'B_ptr' does not name a type
还有一种写法也没有错,就是怪怪的
struct A
{
struct B B_ptr;
};
struct B
{
struct A* A_ptr;
};
这样是不会报错的
其实我们也知道问题出在哪里,于是这么写之后……
#include<stdio.h>
#include<stdlib.h>
int main()
{
struct A;
struct B;
typedef struct A *A_ptr;
typedef struct B *B_ptr;
struct A
{
int ID;
B_ptr B_pointer;
};
struct B
{
int ID;
A_ptr A_pointer;
};
A_ptr x_a=(A_ptr)calloc(1,sizeof(A_ptr));
B_ptr y_b=(B_ptr)calloc(1,sizeof(B_ptr));
x_a->ID=2001;
y_b->ID=2022;
printf("%d %d\n",x_a->ID,x_a->B_pointer->ID);
printf("%d %d\n",y_b->A_pointer->ID,y_b->ID);
free(x_a);
free(y_b);
return 0;
}
//我们用这个闭环链表去互相访问对方的数据
用typedef定义一个3维的指针
然后依次使用那个指针…
示意图
#include<stdio.h>
#include<stdlib.h>
int main()
{
typedef char *ptr1;
typedef ptr1 *ptr2;
typedef ptr2 *ptr3;
//ptr3 arr0[2];
char*** arr0[2];//数组也是一个维度.所以是4维空间
ptr2 arr1[2];
ptr2 arr2[2];
ptr1 arr11[2];
ptr1 arr12[2];
ptr1 arr21[2];
ptr1 arr22[2];
arr0[0]=arr1;
arr0[1]=arr2;
arr1[0]=arr11;
arr1[1]=arr12;
arr2[0]=arr21;
arr2[1]=arr22;
char str[9]="ABCDEFGH";
arr11[0]=&str[0];
arr11[1]=&str[1];
arr12[0]=&str[2];
arr12[1]=&str[3];
arr21[0]=&str[4];
arr21[1]=&str[5];
arr22[0]=&str[6];
arr22[1]=&str[7];
printf("%c\n",*arr0[0][0][0]);
printf("%c\n",*arr0[0][0][1]);
printf("%c\n",*arr0[0][1][0]);
printf("%c\n",*arr0[0][1][1]);
printf("%c\n",*(*(*(*(arr0+1)+0)+0)));
printf("%c\n",*(*(*(*(arr0+1)+0)+1)));
printf("%c\n",*(*(*(*(arr0+1)+1)+0)));
printf("%c\n",*(*(*(*(arr0+1)+1)+1)));
return 0;
}
我们值得执意一下关于
printf("%c\n",*(*(*(*(arr0+1)+0)+0)));
printf("%c\n",*(*(*(*(arr0+1)+0)+1)));
printf("%c\n",*(*(*(*(arr0+1)+1)+0)));
printf("%c\n",*(*(*(*(arr0+1)+1)+1)));
这种表达式
读不懂的代码还无法运行
#include<stdio.h>
#include<stdlib.h>
typedef int (*ptr_func)();//以及函数指针
typedef ptr_func (*ptr_ptr_func)();//返回函数指针的函数指针
ptr_func start(),stop();//简单的函数指针
ptr_ptr_func state1(),state2(),state3(); //一些可以返回函数指针的函数
void state_mac()
{
ptr_ptr_func state=start;//声明一个函数指针变量 ,然后指向了start()函数
while (start != stop)
state= (ptr_ptr_func)(*state)();//先是调用start
}
ptr_func start()
{
return (ptr_func)state1;//返回函数的地址
}
int main()
{
state_mac();
return 0;
}
#include<stdio.h>
#include<stdlib.h>
struct func_thunk start(),stop();//定义了2个返回结构体的函数
struct func_thunk state1(),state2(),state3();
struct func_thunk//这个结构体的成员只有一个,而且还是一个返回结构体的函数指针
{
struct func_thunk(*ptr_func)();
};
void state_mac()
{
struct func_thunk state0={start}; //初始化结构体变量
while(state0.ptr_func!=stop)
state0=state0.ptr_func(); //
}
struct func_thunk start()
{
struct func_thunk ret;
ret.ptr_func=state1;
return ret;
}
int main()
{
state_mac();
return 0;
}
数组大小
关于如何解决数组的大小的传递……现在还没有得到一个满意的答案
标识符的三大属性-避免命名错误
掌握这些规则的话….你就可以对一些二名字重复使用却不会报错
作用域
标识符有效的区域
命名空间
行标 label
也就是你要goto的地方
标签
tag,结构体,联合体枚举类型
结构体/联合体的成员
一般标识符
连接类型
外部连接
一些全局的./非静态的/一些函数
在所有的源文件中有效
(对于为什么是静态的变量,哪些函数我还不太明白)
内部连接
限制在该文件之下的静态变量与函数
无连接
(我们通常定义的那些)的局部变量….如typedef的类型定义
另外有一些关于类型标识符的特殊处理..让它躲避那些规则…详情就看书.P16
关于那些规则..我们要注意
- 不轻易使用以下划线为开头的指针
在C语言的一些库函数中也有一些特殊的宏定义,而不是库函数去处理字符..就像Python那样…P17
关于初始化
一些数据如果不去初始化而去使用,里面有很多的垃圾数据
关于动态内存分配
calloc
可以初始化堆区的数据为0,但是对指着/浮点数的堆区就不会有用
1.34
对于
#include<stdio.h>
int main()
{
char str1[20]="123456";
return 0;
}
对于这种情况….str1是字符串的第一位元素的指针.str1不需要任何操作就是字符串首个元素的指针
编译器也不报错
对于
#include<stdio.h>
int main()
{
char *str2="7890";
return 0;
}
发出警告
[Warning] 不推荐将常量字符串指针转化为char*
什么意思?
在C语言中,一个字符串就可以代表它的地址,这个地址是首位元素的指针
char *str2="7890";
它的寻址是str2寻求那个字符串的指针,然后实现一个对接..于是就可以read访问其中的元素
关于上面提及的问题
#include<stdio.h>
int main()
{
printf("%c%c%c","dqx"[0],"dqx"[1],"dqx"[2]);
return 0;
}
//不会报错,正常执行...输出dqx
一个字符串它会返回首位元素的地址,这个字符串在哪?通常在一个常量区
所以才有那个警告…一个没有用数组方式定义的字符串更像是没有名字的野孩子,还不可调教
发出警告
[Warning] 不推荐将常量字符串指针转化为char*
不推荐把字符串常量(const char * )换为char *
所以
#include<stdio.h>
int main()
{
const char* str="dqx";
return 0;
}
//正常编译不会出错
既然是常量区
所以
#include<stdio.h>
int main()
{
char* str1="dqx";//是否加const都一样
str1[1]='x';
return 0;
}
//在编译时,只是发生警告罢了....运行时会停顿一下发生斩断
这个程序在调试时会发生崩溃…….字符串在常量区,,而你却修改它…发生了冲突
于是报错“说你访问了一块不可修改的内存区域”
二. 结构体,联合.枚举
struct x1 {...};
typedef struct {...} x2;
他们的区别在哪?
x1在使用时更是需要引出struct,告诉编译器这是一个结构体(在.c文件是这样的.cpp可以不用)
x2更加的抽象,,,使用时不需要用structu
typedef struct x
{
int ID;
char *name;
}x;
这样写编译器会报错吗??
前面我们已经对这个问题做了大概的了解,,,这样写没有错..但是不怎么方便使用
数组加长
这个只是静态的数组加长
在动态输入的时候仍然不可以实现…..
可能还是要链表吧?
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct name
{
int len;
char str[1];
};
struct name *make_name(char*new_name)//一个接受字符串.返回结构体指针的函数
{
struct name *ret=(struct name *)malloc(sizeof(struct name)-1+strlen(new_name)+1);//把数组的 str[1]取代,换上len+1的长度
if(ret!=NULL)//为什么这个结构体可以修改长度..因为他在堆区呀...
{
ret->len=strlen(new_name);
strcpy(ret->str,new_name);
}
return ret;//这个S指针的指向值 长度与D指针指向的长度完全不一样...
}
int main()
{
char x1[]="dqx";
char x2[]="ywj";
struct name *n1= make_name(x1);//其实的话..好比一个int*指针可以接受一个long long*的指针吗?似乎不可以..但这里也是一个意思..
struct name *n2= make_name(x2);
puts(n1->str);
puts(n2->str);
free(n1);
free(n2);
return 0;
}
//引发了我的指针的思考.....指针类型的相互转化
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct name
{
int len;
char *str;
};
struct name *make_name(char*new_name)//一个接受字符串.返回结构体指针的函数
{
int new_len=strlen(new_name);
int struct_len=sizeof(struct name);
char *buff=(char*)malloc(struct_len+new_len+1);
struct name *ret=(struct name *)buff;
ret->len=new_len;
ret->str=buff+struct_len;
strcpy(ret->str,new_name);
return ret;
}
int main()
{
char x1[]="dqx";
struct name *n1= make_name(x1);//其实的话..好比一个int*指针可以接受一个long long*的指针吗?似乎不可以..但这里也是一个意思..
puts(n1->str);
free(n1);
return 0;
}
//引发了我的指针的思考.....指针类型的相互转化
三. 表达式
有些值既是左值又是右值
#include<stdio.h>
int main()
{
int arr[10]={9,9,9,9,9,};
int i=0;
arr[i]=i++;
printf("%d\n",arr[0]);
printf("%d\n",arr[1]);
return 0;
}
所以你的结果是什么?
不太的编译器会给出不太的答案
Mingw
0
9
请按任意键继续. . .
GCC
9
0
--------------------------------
Process exited after 0.009451 seconds with return value 0
请按任意键继续. . .
对于
#include<stdio.h>
int main()
{
int sb=2;
printf("%d\n",sb++*sb++);
printf("%d\n",sb++*sb++);
return 0;
}
Mingw
4
16
请按任意键继续. . .
GCC
6
20
--------------------------------
Process exited after 0.01203 seconds with return value 0
请按任意键继续. . .
#include<stdio.h>
int main()
{
int sb=10;
sb=sb++;
printf("%d\n",sb);
return 0;
}
在我这里2个答案都是10
….woc,,,
逻辑判断符的短路效应
通俗的讲就是..如果判断一部分就知道整体了..那么就不会在进行判断了
对于&,|,!
通常会把所有的条件给判断个遍…
下面这个Code就hi让程序崩溃
#include<stdio.h>
int main()
{
int x=10,y=0,z=5;
int* p=NULL;
if(y!=0&x/y>z)//0不能作为除数
puts("yes");
else
puts("no");
if(p==NULL|*p=='\0')//NULL的这内存区域是不可读取不可写入的
puts("yes");
else
puts("no");
return 0;
}
如果你用&&,||,!
的话,就不会存在上面的系统崩溃
关于i++与++i
在调用最后的结果时..就就是说i++与++i最后的结果都是一样的
在掉用当时的结果时,也就是说a=i++与a++i会有所不同
运算时的类型转化
#include<stdio.h>
int main()
{
int a=1000,b=1000;
long int c=a*b;
printf("%ld",c);
return 0;
}
这个表达式的意思是…..
int x int
然后将结果转化为long
再赋值给long
//这里的int x int 这里的结果很可能超过了long int的范围.....有漏斗
#include<stdio.h>
int main()
{
int a=1000,b=1000;
long int c=(long)a*b;
printf("%ld",c);
return 0;
}
这个代码的不同之处…..a是被显示的转化为long类型,在计算时,b被隐式的转化为long类型,于是得到的数据会<=long_max*long-max
这个代码等价于
#include<stdio.h>
int main()
{
int a=1000,b=1000;
long int c=(long)a*(long)b;
printf("%ld",c);
return 0;
}
但是有一种很蠢得写法
#include<stdio.h>
int main()
{
int a=1000,b=1000;
long int c=(long)(a*b);
printf("%ld",c);
return 0;
}
写了就像没有写一样…(a*b)
本来就会隐式的转化为long类型,,无法提前阻止危险的发生
好比下面的代码也是这样
#include<stdio.h>
int main()
{
double x=0,y=34;
x=(double)(5/9)*(y-32);
printf("%lf",x);
return 0;
}
三目运算符的返回值利用
通常情况下我们的三目运算符会返回一个值,而不是变量..我们却可以用指针去模拟它
#include<stdio.h>
int main()
{
int a=20;
int b=30;
*(a>25?(&a):(&b))=100;
printf("a:%02d b:%02d \n",a,b);
return 0;
}
输出
a:20 b:100
--------------------------------
Process exited after 0.0157 seconds with return value 0
请按任意键继续. . .
若类型不一致,会转换成int比较
int 可以表示 char, unsigned char, short, unsigned short 的所有值
眼过千遍不如手过一遍! 书看千行不如手敲一行! 手敲千行不如单步一行! 单步源
大小不一致,故编译器选择了值保护规则
提升的类型和之前的无符号类型一样大,则和无符号保护规则一致。
#include <stdio.h>
int main()
{
char c1 = 0x88;//c1本来就是负数
unsigned char uc1 = 1;
short s1 = 0x8888;//本来就是负数
unsigned short us1 = 1;
int n1 = 0x88887777;//本来就是负数
unsigned int un1 = 0x81007777;//本来就是负数
if(c1>uc1)
printf("%d>%d\n",c1,uc1);
else
printf("%d<%d\n",c1,uc1);
if(s1>us1)
printf("%d>%d\n",s1,us1);
else
printf("%d<%d\n",s1,us1);
if(n1>un1)
printf("%d>%d\n",n1,un1);
else
printf("%d<%d\n",n1,un1);
}
值保护与无符号保护
#include<stdio.h>
int main()
{
unsigned short int a=10;
int b=-5;
if(b>a)
printf("%d>%d 无符号保护规则\n",b,a);
else
printf("%d<%d 值保护规则\n",b,a);
printf("int %d-----unsigned short int %d",sizeof(int),sizeof(unsigned short int));
return 0;
}
对于有符号的int,long,long..4字节以及以上的类型
一个比它<它
的无符号类型,,带正负比较大小
一个>=它
的无符号类型,采用正数比较大小
对于有符号的short,.char 小于4字节以及以下的类型
一个比它<=它
的无符号类型,,带正负比较大小
一个>它
的无符号类型,采用正数比较大小
值保护规则:带有符号位
的比较
无符号保护规则: 转化
为正数比较
#include<stdio.h>
int main()
{
unsigned char x=0x80;//127=0x7f
unsigned long y=0;
y|=x<<8;
printf("0x %lx ",y);//你用%x也无所谓
return 0;
}
在这个代码中…x会被转化为unsigned long
最后输出0x8000
运算符优先级
过多的括号会引起难以读取,,掌握一定的顺序也有一定的好处
左上的优先级大
()、[ ]、->、 .(点)
!、~、++、--、type、*、&、sizeof、
*、 / 、 %
+、-、
<<、>>
<、<=、>、>=、
==、!=、
&、
^、
|、
&&、
||、
Exp1<Exp2 ? Exp3 : Exp4
,
=、+=、-=、*=、/=、%=、>>=、<<=
四. 指针
char* 访问int数据
#include<stdio.h>
int main()
{
int arr[5]={'d','q','x','i','s'};
char *p=NULL;
int i=0;
for(i=0;i<5;i++)
{
p=(char*)((int*)arr+i);//这里的int*有点多于
puts(p);//arr+i之所以可以指向下一个单元,,,是因为它本来就是int*
}
return 0;
}
关于多级指针的实用
#include<stdio.h>
int* func(int**);
int main()
{
int x=100;
int *ip2=&x;
int **ip1=&ip2;
printf("%d\n",**ip1);
return 0;
}
错误的用法是
int *ip2=&x;
int **ip1=ip2;
这里让ip1的地址直接指向了x而不是ip2
通过函数修改指向,而不是指向的值//
#include<stdio.h>
void f(int** ipp)
{
static int y=8;
*ipp=&y;
}
int main()
{
int a=4;
int *ip=&a;
printf("%08X -> %d\n",ip,*ip);
f(&ip);
printf("%08X -> %d",ip,*ip);
return 0;
}
通过函数的形式修改指向
一般的话..我们修改指针的指向
int a=10;
int b=20;
int *ptr=&a;//这里的指向是a
ptr=&b;//这里的指向是b
那么我们这么拖过传入的参数去进行一个指向的修改???
用到二级指针的概率
方式一
#include<stdio.h>
void f(int** ipp)//所以我们这里做了一个二级指着..另外ipp也只不过是一个数据的拷贝
{
static int y=8;//我们无法修改ipp的值,就像我们传递进来一个数据0x1234,我们不可能对数据0x1234进行一个修改
*ipp=&y; //我们却可以对*(0x1234)的指向进行一个修改
}
int main()
{
int a=4;
int *ip=&a;
printf("%08X -> %d\n",ip,*ip);
f(&ip);//我这里传递的就是一个指针的指针 ,ip是指针,我有吧指针的地址传进去了
printf("%08X -> %d",ip,*ip);//所以调用了函数后我们就修改了ip的指向...
return 0;
}
//我们无法改变一个地址...但是我们可以更改指向....!!!你不可能把a=10当中a的地址做一个更改
0062FE1C -> 4
00403010 -> 8
--------------------------------
Process exited after 0.01201 seconds with return value 0
请按任意键继续. . .
方式二
#include<stdio.h>
int* f()
{
static int y=8;
return &y;
}
int main()
{
int a=4;
int *ip=&a;
printf("%08X -> %d\n",ip,*ip);
ip=f();
printf("%08X -> %d",ip,*ip);
return 0;
}
0062FE14 -> 4
00403010 -> 8
--------------------------------
Process exited after 0.009186 seconds with return value 0
请按任意键继续. . .
五. 空指针
六. 数组与指针
首先数组!=指针…这你也知道…
如果你在一个
main.c文件中定义了
char str[100];
在othrt.c的文件中
你可以
extern char str[100];
但是不不能
char* str;
关于指针与数组的区别
浅浅的说一下
char *p1="dqx";
char p2[]="okj";
然后看到
p1是寻址一个地址,那个地址是“dqx”字符串的首地址
p2是不需要寻址,他就是“okj”的第一个元素的地址
这是与编译有关的原理
数组
空间分配是确定的…不能修改空间大小
它的地址是常量..不可修改
指向一块内存区的指针
可以作为数组使用….
空间大小可以人为的DIY..可以动态内存分配
它可以指向任意一块内存的地址
数组和指针的编译方式可能不同…
关于字符串数组的不能初始化
数组这个东西我们一般不能直接赋上一大坨值
#include<stdio.h>
#include<string.h>
int main()
{
char arr[10];
//arr="dqx";//这会报错.....
strcpy(arr,"dqx");//但是这样就可以..就离谱
puts(arr);
}
但是我们这样做
#include<stdio.h>
void init_str(char *);
int main()
{
char arr[10]={0};
printf("%08X\n",arr);
init_str(arr);
printf("%s",arr);
}
void init_str( char *str )
{
printf("%08X\n",str);
if(str!=NULL)
str="dqx";
else
puts("emmmm");
printf("%08X\n",str);
}
这一段代码看似没有啥问题..输出“dqx”
但是
假设arr=0x1234,str进来也是0x1234(事实如此)
然后str=“dqx”…
于是str=0x5678;
那么这就明白为什么啥也不输出了`
你传递过去的是一个地址
这个地址有变量来接受..在函数当中,,,,,,这个变量的地址又发生了变化
数组指针
它可以用数组的形式去遍历..然后用指针访问
指针数组貌似也是哈哈….
原理图
(*ptr2)[0],
好比这个….以数组[0]
的形式找到那个指针..然后用(*ptr2)
去访问
值得注意的是它是怎么用的
#include<stdio.h>
int main()
{
char str1[6]={'X','Y','Z','O','P','Q'};//地址 32-47
char str2[6]={'G','H','I','J','K','Z'};//地址 16-31
char str3[6]={'A','B','C','D','E','F'};//地址 0 -15
char *ptr1=str1;
char (*ptr2)[6]=str3;
printf("%c\n",*ptr1);
ptr1++;//这里的+1是一个字节
printf("%c\n",*ptr1);
//对数组指针
printf("%c %c\n",(*ptr2)[0],(*ptr2)[1]);
ptr2+=3;//为什么我会+3..
//这里的+3是+3*6=18;
printf("%c %c",(*ptr2)[0],(*ptr2)[1]); //注意运算符优先级地重点,,很容易出错
return 0;
}
为什么我会ptr2+3
关于地址这个东西我写在上面的…..
ptr2的地址最小
然后str2,str3,str1他们的地址相差16,也就是一行数据段的空间大小
所以的话
ptr2+3代表ptr的字节位置移动了18个
移动到[15]代表遍历完str3
移动[16] ,[17] ,[18],就遍历到元素I
为什么sizeof不可以给出数动态内存数组的大小….
sizeof能够返回数组大小的情况,,,?
如果数组的大小未知或者已经退化为指针,,则它不能提供数组的大小
sizeof(char*)==8
siezof(inti*)==8
那些是指针的大小
动态内存分配多维数组
它的原理就是动态内存分配一个二维数组
big的大小也是动态分配…
big每个元素的指向也是动态内存分配的`
#include<stdio.h>
#include<stdlib.h>
int main()
{
int** big=(int**)malloc(sizeof(int*)*5);
int i=0,j=0,x='A';
for(i=0;i<5;i++)
big[i]=(int*)malloc(sizeof(int)*6);
for(i=0;i<5;i++)
for(j=0;j<6;j++)
big[i][j]=x++;
for(i=0;i<30;i++)
printf("%c ",*((*big)++));
return 0;
}
这样做的话..导致动态内存分配的空空间不;连续
里面的最底层元素之间的间距是很大的,.也就是不连续
上面的每一个方框块都是分配出来的,,,,我们开辟了6个堆区..难以实现这几个堆区的地址连续衔接
输出乱的,
A B C D E F T G H I J K L T M N O P Q R T S T U V W X
--------------------------------
Process exited after 0.03308 seconds with return value 0
请按任意键继续. . .
我们可以开辟2个堆区
堆区与堆区之间的地址不是连续的
但是堆区内部的单元是地址连续的
我么只是开辟了2个堆区
#include<stdio.h>
#include<stdlib.h>
int main()
{
int** ptr1=(int**)malloc(5*sizeof(int*));
int* ptr2=(int*)malloc(5*10*sizeof(int));
int** buff=ptr1;
int i=0,j=0,x='A';
for(i=0;i<5;i++)
ptr1[i]=ptr2+i*10;//让高阶堆区指向连续的地址
//初始化堆区
for(i=0;i<50;i++ )
(*ptr1)[i]=x++;
for(i=0;i<50;i++ )
printf("%c ",*((*ptr1)++));
//printf("%c ",(*ptr1)[i]);//也行
return 0;
}
输出
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r
--------------------------------
Process exited after 0.01136 seconds with return value 0
请按任意键继续. . .
用动态内存分配一个三维正方体
#include<stdio.h>
#include<stdlib.h>
void show();
int main()
{
int x=3,y=3,z=4;
char*** Cube=(char***)malloc(x*sizeof(char));
int i,j,k,ascii='A';
for(i=0;i<x;i++)
{
Cube[i]=(char**)malloc(y*sizeof(char*));
for(j=0;j<y;j++)
{
Cube[i][j]=(char*)malloc(z*sizeof(char));
for(k=0;k<z;k++)
Cube[i][j][k]=ascii++;
}
}
show(Cube);
return 0;
}
void show(char*** Cube)
{
int x=3,y=3,z=4;
int i,j,k;
for(i=0;i<x;i++)
{
for(j=0;j<y;j++)
{
for(k=0;k<z;k++)
{
printf("%c ",Cube[i][j][k]);
}
free(Cube[i][j]);
puts("");
}
free(Cube[i]);
puts("");
}
}
实现数组从1开始计数的操作
#include<stdio.h>
#include<stdlib.h>
int main()
{
int arr[10];
int *ptr=&arr[-1];
int i=0;
int ascii='A';
//从小标为1的开始赋值
for(i=1;i<=10;i++)
ptr[i]=ascii++;
//我们试一下原来的数组是否导入
for(i=0;i<10;i++)
printf("%c ",arr[i]);
return 0;
}
输出
A B C D E F G H I J
--------------------------------
Process exited after 0.01377 seconds with return value 0
请按任意键继续. . .
七. 内存分配
这段代码写的有点做作…..
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
char str[10],*ptr=NULL;
fgets(str,10,stdin);
ptr=strchr(str,'\n');//你的输入截断在换行那里
if(ptr!=NULL)
//*ptr='\0';//手动设置空字符
puts(str);
return 0;
}
这段代码他说我只是分配了一行的内存????
为什么运行没问题呀,,,3行输出
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
FILE* fp=fopen("D:/in.txt","r");
char linebuf[80];
char* line[100];
int i;
for(i=0;i<100;i++)
{
char* p=fgets(linebuf,80,fp);
if(p==NULL)
break;
line[i]=p;
puts(line[i]);
}
fclose(fp);
return 0;
}
局部变量返回的解决方案
首先关于一般的auto局部变量,他曾在栈里面,会被清空..所以它的地址我们不可以返回,它的数值可以
然后书上说不推荐使用static….它是静态数据不可写入,,,调用者还不能多次调用static所在的函数,否者会引起数据覆盖
推荐使用已经分配好的内存
如果用堆区记得释放
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char* func1(int n);//wrong
char* func2(int n,char*);//safe
char* func3(int n);//static
char* func4(int n);//malloc
int main()
{
int n=50;
char *ptr1=NULL;
char ptr2[10];
char *ptr3=NULL;
char *ptr4=NULL;
//ptr1=func1(n);
func2(n,ptr2);
ptr3=func3(n);
ptr4=func4(n);
printf("%s %s %s ",ptr2,ptr3,ptr4);
free(ptr4);
return 0;
}
/*
char* func1(int n)
{
char buff[10]={0};
sprintf(buff,"%d",n);
return buff;//函数的局部会被清空..不要返回
}
*/
char* func2(int n,char* str)
{
sprintf(str,"%d",n);
return str;
}
char* func3(int n)
{
static char buff[10]={0};
sprintf(buff,"%d",n);
return buff;
}
char* func4(int n)
{
char *buff=(char*)malloc(10*sizeof(char));
sprintf(buff,"%d",n);
return buff;
}
size_t类型问题
malloc的参数是size_t类型,他被定义成了unsigned long…如果你传入一个不是同类型的值,maloch会返回垃圾
sizeof的返回值类型也是unsigne long…所以的话..输出时注意i但
printf("%lu",(unsigned long)sizeof(int));
recallloc
他会扩展堆区的长度
如果realloc可以在原来的地址上找到足够的堆区,,那么他就返回原来的指针
也就是说说它不在原来的地址上跟着衔接..
这是可能罢了
.但是我们可以避免万一的清空
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
char* str=(char*)malloc(5*sizeof(char));
char*buff=(char*)realloc(str,10*sizeof(char));
if(str!=NULL)
str=buff;
else
puts("wrinig");
}
如果我故意让伸长的堆区长度变得更加长……但是地址又发生了改变呢?????
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
char *S,*buff,*new_ptr;
int arr_offset=0;
S=(char*)malloc(5*sizeof(char));//分配内存
strcpy(S,"Dqx,");//写入
new_ptr=strchr(S,',');//寻址下一个写入的位置
buff=(char*)realloc(S,20);//扩展空间,,指针用buff接受
arr_offset= new_ptr-S;//计算写入位置到指针头的偏移量
if(buff!=NULL)
{
S=buff;//把新扩展的指针头传递给S
buff=S+arr_offset;//这里的buff也就真的是一个临时的变量
strcpy(buff," good");
}
puts(S);
free(S);
return 0;
}
用recallc实现动态的内存分配
#include<stdio.h>
#include<stdio.h>
#include<stdlib.h>
char* func(FILE *fp);
int main()
{
char* out=NULL;
FILE *fp=fopen("D:/in.txt","a+");
if(fp!=NULL)
out=func(fp);
puts(out);
fclose(fp);
free(out);
return 0;
}
char *func(FILE *fp)
{
char *temp=NULL,*ret=NULL;//因为realloc的不确定性所以我们用到临时指针
int index=0,max_expand=0;
char c=0;
while((c=getc(fp))!=EOF)
{
if(index>=max_expand)//这里如果我们已有内存用超了就再分配
{
max_expand+=20;
temp=(char*)realloc(ret,max_expand);//为什么+1?
if(temp==NULL)
return 0;
}
ret=temp;
ret[index]=c;
if(c=='\n')
{
ret[index]='\0';//把最后那个换行符截断
break;
}
index++;
}
return ret; //反对堆区的指针
}
书山写的
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char* agetline(FILE *fp);
int main()
{
FILE* fp=fopen("D:/in.txt","a+");
char* ptr;
if(fp!=NULL)
ptr=agetline(fp);
fclose(fp);
puts(ptr);
return 0;
}
char* agetline(FILE *fp)
{
char *retbuf=NULL;
size_t nchmax =0;
register char c=0;
size_t nchread =0;
char *newbuf=NULL;
while((c=getc(fp))!=EOF)//逐个读取文件到文件末尾
{
if(nchread>=nchmax)//max会不断的+20,有一天会超过它 //最开始2者为0
{
nchmax+=20;
/*if(nchread>=nchmax)//当你的读取字节大于我的max
{
free(retbuf);
return NULL;
}*/
newbuf=realloc (retbuf,nchmax+1);
/*if(newbuf==NULL)
{
free(retbuf);
return NULL;
}*/
retbuf=newbuf;
}
if(c=='\n')
break;
retbuf[nchread++]=c;//文件里面读取到的东西都进入了这里面来 ,
}
if(retbuf!=NULL)
{
retbuf[nchread]='\0';
//newbuf=realloc(retbuf,nchread+1);
//if(newbuf!=NULL)
//retbuf=newbuf;
}
return retbuf;
}
calloc与malloc的区别
calloc
的解释是
p=malloc(m*n);
memset(p,0,m*n)
于是就这个区别….
calloc的0值有时候不能代表空指针NULL,还是得自己动手
八. 字符和字符串
九. 布尔表达式与变量
十. 预处理器
很复杂
十一. ANSI/ISO标准C
const 限定词
适用范围
const 限定的对象是运行中不能被赋值的对象…于是他就说它并不是一个完全的真正的常量,,,,,
于是它不能用于
数组维度,case行标,,,,,
const指针
从里到外看,会更加明白
const出现在 * 左边,表示被指物是常量;
如果出现在 * 右边,表示指针本身是常量;
如果出现在 * 两边,表示被指物和指针都是常量。
const char * p;
const在左,常量值
char const *p
const在左,常量值
char * const p
const在右,常量指针
#include<stdio.h>
int main()
{
int x=1;
const int* p=&x;
const int** pp=&p;
printf("%d",**pp);
}
这一段代码标准的没问题
到了const int*一级的也好好
到了二级的指针就水深了…规律还很乱
#include<stdio.h>
int main()
{
int x=1;
const int xx=2;
int *p1=&x;
int **pp1=&p1;
int *** ppp1=&pp1;
const int* p2=&x;
//*p1==x,*p1不可以修改,x却可以修改
const int **pp2=&p2;
const int***ppp2=&pp2;
//ppp2=&pp1;
//*ppp2=pp1;
//pp2=&p1;
*pp2=p1;
p2=p1;
//*p2=10;
}
typedef指针与const
只是提一下
typedef char* dqx;
const dqx var;
var最后不是const char *
main函数使用
…..
其他的ansic问题
#pragma one
详情见书籍P123
当头文件被多次包含的时候,他会确保其内容只会被处理一次…
&arr与arr的区别
假设
int arr[10];
书上说
数组的名字代表了可以遍历整个数组的地址
而&arr或者&arr[0]仅仅代表了首元素的地址
#include<stdio.h>
#include<string.h>
int main()
{
int arr[3]={1,2,3};
char str[10]="dqx";
printf("%08X\n",arr);
printf("%08X\n",&arr);
printf("%08X\n",&arr[0]);
printf("%d\n",sizeof(arr) / sizeof(arr[0]));
printf("%d\n",sizeof(&arr) / sizeof(arr[0]));
printf("%d\n",sizeof(&arr[0]) / sizeof(arr[0]));
printf("%08X\n",str);
printf("%08X\n",&str);
printf("%08X\n",&str[0]);
printf("%d\n",strlen(str));
puts("");
//printf("%d\n",strlen(&str));//Mingw64直接报错 .gcc只是警告
printf("%d\n",strlen(&str[0]));
}
可以看出….那一个地址的是一模一样的
但是函数处理他们的结果确是不一样的
值得注意的是
求数组的长度
是sizeof(arr)/sizeof(arr[0])
不是其他的形式
memmove()与memcpy()
#include<stdio.h>
#include<string.h>
void* memmove(void* D,void const *S,size_t len );
int main()
{
char S[10]="dqxisgood";
char D[10];
memmove(D,S,10);
puts(D);
}
void* memmove(void* D,void const *S,size_t len )
{
register char*d=D;
register char const* s=S;
//下面都是一个简单的利用指针赋值的操作
if(d<s)
while(len-->0)
*d++=*s++;
else
{
d+=len;
s+=len;
while(len-->0)
*--d=*--s;
}
return D;
}
现在我都还不知道为什么官方他要那么去写…………………..
#include<stdio.h>
char* memmove(char* D,char *S,size_t len );
int main()
{
char S[10]="dqxisgood";
char D[10];
memmove(D,S,10);
puts(D);
}
char* memmove(char* D,char *S,size_t len )
{
char*d=D;
char*s=S;
while(len-->0)
*d++=*s++;
return D;
}
十二. 标准输入与输出
EOF
EOF-->Int 类型
从 EOF 宏的定义中可以看出,EOF 宏的值为 -1,貌似他不会发生什么变化
属于 int 类型的数据,在 32 位系统中,可以表示为 0xFFFFFFFF。
有符号的char才是让EOF停下来
无符号的char就不得行,,无符号的char>0,但是我们的EOF为-1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
char a='\xff';
char b='\377';
char c=255;
unsigned char d=255;
unsigned char e='\377';
unsigned char f='\xff';
//char x=0;
//while((x=getchar())!=EOF);
while(a==EOF)
{
printf("OK,I am Fine.");
if(b==EOF)
if(c==EOF)
break;
};
puts("");
while(d==(unsigned char)EOF)
{
printf("OK,I am Fine.");
if(e==(unsigned char)EOF)
if(f==(unsigned char)EOF)
break;
};
puts("\nGood...");
return 0;
}
书上是这么说的
如果char是有符号的,对于有符号的-1是正数的255,也是‘\xff’,’\377’
所以EOF碰到她们就会结束
如果char是是正数
EOF会变成255(oh??是吗?我的文件并没有,EOF还是-1)
于是你的unsigned char不可能与我的-1相等.于是你的输入就不停止
输出字符%
#include<stdio.h>
int main()
{
printf("%%");
return 0;
}
他会输出
%
--------------------------------
Process exited after 0.02536 seconds with return value 0
请按任意键继续. . .
*域宽度
#include<stdio.h>
int main()
{
long int x=21;
printf("%0*ld",4,x); //0是人为加上去的,不写就是空格,4是宽度
return 0;
}
#include<stdio.h>
int main()
{
long int x=21;
printf("%04ld",x); //0不写就是空格
return 0;
}
上面2个写法的输出都是
0021
--------------------------------
Process exited after 0.009707 seconds with return value 0
请按任意键继续. . .
用千位一个都好输出货币
代码无法执行,,,,.在p135页
涉及一些函数
#include<stdio.h>
#include<locale.h>
char* commaprint(unsigned long n);
int main()
{
unsigned long x=123456789;
char *str;
str=commaprint(x);
puts(str);
return 0;
}
char* commaprint(unsigned long n)
{
static int comma='\0';
static char retbuf[30];
char *p=&retbuf[sizeof(retbuf)-1];//让p指针指向了最后一位
int i=0;
if(comma=='\0')//这里一定会进入....为什么要这样设置
{
struct lconv *lcp=localeconv();
if(lcp!=NULL)
{
if(lcp->thousands_sep!=NULL&&*lcp->thousands_sep!='\0')
comma=*lcp->thousands_sep;
else
comma=',';
}
}
*p='\0';
do
{
if(i%3==0&&i!=0)
*--p=comma;
*--p='\0'+n%10;
n/=10;
i++;
}
while(n!=0);
return p;
}
输出浮点数
对于float还是double..最后pritnf输出的都是double的型
这期间存在一个类型的自动转化,,,,float->double
scanf
#include<stdio.h>
#include<stdlib.h>
#define max 10
int main()
{
int x=0;
int y=0;
scanf("%d\n",&x);
scanf("%d\n",&y);
printf("%d %d",x,y);
return 0;
}
输出
1
2
3
1 2
--------------------------------
Process exited after 1.896 seconds with return value 0
请按任意键继续. . .
为什么?????
在3之前他才会真正的寻找到空字符!!!!!!!!!!!!!
使用scanf域gets的茬
#include<stdio.h>
#include<stdlib.h>
#define max 10
int main()
{
int n=97;
char str[80];
puts("输入1");
scanf("%d",&n);
puts("输入2");
gets(str);
printf("%s-->%d",str,n);
return 0;
}
gets有个原则
遇到换行符就停止读取…….
例子1
输入12然后换行
输入1
12
输入2
-->12
--------------------------------
Process exited after 4.625 seconds with return value 0
请按任意键继续. . .
你的stdin是12和换行符号
成功读取了12后,就flussh了一下剩下了换行符????????是吗??我保持意见
于是gets读取换行符后直接退出
所以才hi有上面的结果
例子2
输入66空格再dqx
输入1
66 dqx
输入2
dqx-->66
--------------------------------
Process exited after 4.964 seconds with return value 0
请按任意键继续. . .
你的stdin中成功的去66后flussh一下剩下空格与dqx
然后就读入到str中
针对上面的情况..我们最好都用scanf或者都用gets
但是改进方案
#include<stdio.h>
#include<stdlib.h>
#define max 10
int main()
{
int n=97;
char str[80];
puts("输入1");
scanf("%d",&n);
fflush(stdin);
puts("输入2");
gets(str);
printf("%s-->%d",str,n);
return 0;
}
这样就不会存在吸收多于的空格..你的空格会被flush
给修改
scanf的缓冲区刷新
如果不按照scanf的%格式输入的话..他会跳出scanf.而在stdin’中的数据并没有消失,当遇到下一个scanfh回被读取
所以的话
对于代码
#include<stdio.h>
#include<stdlib.h>
int main()
{
int n=0,flag=0;
while(1)
{
flag=scanf("%d",&n);
if(flag!=1)
puts("input again");
else
break;
}
printf("%d\n",n);
return 0;
}
如果你输入字符‘X’,他会无限的循环,,,不会停止,,你无法输入
就是因为你的‘X’不是按照它的格式,,,没有被它读取
如果加上fflush,刷新一下stdin..就有刷新当前的输入…(相当于把以前的数据晴空)
#include<stdio.h>
#include<stdlib.h>
int main()
{
int n=0,flag=0;
while(1)
{
flag=scanf("%d",&n);
if(flag!=1)
{
puts("input again");
fflush(stdin);
}
else
break;
}
printf("%d\n",n);
return 0;
}
于是你不按照格式输入也不会无限循环
其它的输入函数
sprintf
int sprintf(char *str, const char *format, ...)
关于他是怎么用的……不做详细的阐述..具体的话到了时间再说
出现的问题
sprintf不能确定缓冲区大小./…可能存在内存溢出后改写其它的内存区..我们可以用到
%.NS,%.*S
但是我们还有一个函数
snprrintf
相比于sprintf…这个函数做了限制
还有一种方法…那数据写入文件…之后再拿出..
把数据写入动态内存分配
sprintf()也会有返回值..至于是什么自己查看
gets
char *gets(char*);
他会读取字符串…一直遇到换行或者EOF时结束
于是把你的换行符号丢弃
加上空字符‘\0’
如果读入成功,则返回与参数 buffer 相同的指针;
如果读入过程中遇到 EOF 或发生错误,返回 NULL 指针。
因此,在遇到返回值为 NULL 的情况,要用 ferror 或 feof 函数检查是发生错误还是遇到 EOF。
根据运行结果,当用户在键盘上输入的字符个数大于缓冲区 buffer 的最大界限时,gets 函数也不会对其进行任何检查,因此我们可以将恶意代码多出来的数据写入堆栈。可能成为病毒的入口
fgets
char *fgets(D,len,S);
即给定参数 n=len,fgets 函数只能读取 n-1 个字符(包括换行符)。如果有一行超过 n-1 个字符,那么 fgets 函数将返回一个不完整的行(只读取该行的前 n-1 个字符)
缓冲区总是以 null('\0') 字符结尾,对 fgets 函数的下一次调用会继续读取该行。
也就是说,每次调用时,fgets 函数都会把缓冲区的最后一个字符设为 null('\0'),这意味着最后一个字符不能用来存放需要的数据。所以如果某一行含有 size 个字符(包括换行符),要想把这行读入缓冲区,要把参数 n 设为 size+1,即多留一个位置存储 null('\0')。
总而言之就是他会把给定大小的最后一个位置设计为哦‘\0’
如果你非要写那么长,就得分配len+1..
fflush(sdtdin)
书上说fflush时对输出stdout有效的
这点勾起了我对count<<“Ok.I am Fine”<<endl;
确实也是哈…fflush是对stdout的一个实现
那么真的对stdin无效….也不是
有些编译器实现了fflush(stdin)放弃未读取的字符..不过这里面应该还有更加深奥的东西….有一本书叫做<C标准库>…哇我…
文件读写
涉及到指针的函数封装(容易出错)
对指针的理解不是很到位的话
#include<stdio.h>
#include<stdlib.h>
#define max 10
void func(FILE *fp);
int main()
{
FILE *fp1=NULL;
func(fp1);
fp1;
}
void func(FILE *fp2)
{
fp2=fopen("D:/in.txt","w");
}
这个函数对main的fp1不会有任何的影响……main的fp还是NULL
但是我们非要那么干的话
方法一
书上是这样写的,..但是函数岂不是返回了一个局部变量??????????有冲突
#include<stdio.h>
#include<stdlib.h>
FILE* func(FILE *fp2);
int main()
{
FILE *fp1=NULL;
fp1=func(fp1);
fp1;
}
FILE* func(FILE *fp2)
{
fp2=fopen("D:/in.txt","w");
return fp2;
}
方法二..引入二级指针..这才是解释你之前那个为什么是错的了…你传入的只是一个副本罢了
#include<stdio.h>
#include<stdlib.h>
void func(char*,FILE **fp2);
int main()
{
FILE *fp1=NULL;
func("D:/in.txt",&fp1);
}
void func(char* filename,FILE **fp1)
{
*fp1=fopen(filename,"w");
}
或者
#include<stdio.h>
#include<stdlib.h>
void func(FILE **fp2);
int main()
{
FILE *fp1=NULL;
func(&fp1);
printf("%p\n",fp1);
}
void func(FILE **fp1)
{
*fp1=NULL;
*fp1=fopen("D:/in.txt","w");
if(*fp1!=NULL);
printf("%p\n",*fp1);
}
十三 库函数
从字符串中如何去除子字符串
好比dqx is good
中去除字符串is
way1
#include <stdio.h>
#include <string.h>
int main()
{
char S[40]="dqx is good";
char D[12];
strncpy(D, S+4, 2);
D[6]='\0';
puts(D);
return(0);
}
标签:,main,return,int,char,printf,include
From: https://www.cnblogs.com/re4mile/p/17013325.html