c语言初阶
1基本数据类型
在C语言中,除了有无符号关键字unsigned,还对应有一个有符号关键字signed,只是通常省略不写。此外,除了char,其他整型类型后面可以加上关键字int表示整型,通常也是省略不写。下面是各种类型其他对应写法。
1.1整数常量后缀
没有后缀的整数常量默认为int,只能表示21亿左右的数值,如果超过就会警告,执行就会出错,因为int无法表示超出的数字。
后缀基本为类型第一个字符的大小写
如:L/l与U/u组合 unsigned long
1.2浮点数常量后缀
没有后缀的浮点数默认为double。
后缀、 | 类型 |
---|---|
F/f | long double |
F/f | float |
1.3科学记数法
表示方式:尾数部分e指数部分或者尾数部分E指数部分
使用科学计数法表示数字更加简单。
2数组
2.1初始化
2.1.1整体初始化
代码 | 结论 |
---|---|
int arr[12]; | 1. 数组未初始化,数组里面的值都是随机值 |
int arr[12] = {0}; | 2. 数组初始化为{0},数组里面的值都是0。 |
int arr[12] = {2}; | 3. 数组初始化为{非零值},数组里面第一个值是非零值,其他的值都是0。 |
2.1.2部分初始化
代码 | 结论 |
---|---|
int arr[12] = {[2]=2,[5]=5}; | 指定下标的值被赋值,其他的值都是0。这是C99语法。 |
2.1.3大小
数组大小=元素大小*数组个数。
char carr[12];
int iarr[12];
double farr[12];
printf("carr = %d\n",sizeof(carr));
printf("iarr = %d\n",sizeof(iarr));
printf("farr = %d\n",sizeof(farr));
结果
carr = 12
iarr = 48
farr = 96//12*8
注意
int days[]={31,28,31,30,31,30,31,31,30,31,30,31};
int arr = days;
警告
douhao.c:24:11: 警告:初始化将指针赋给整数,未作类型转换 [默认启用]
int arr = days;
printf("days[0]=%p\n",&days[0]);
printf("days=%p\n",days);
printf("arr=%d\n",arr);
结果
days[0]=0x7ffeb293c000
days=0x7ffeb293c000
arr=-1298939904
结论
????????????????
2.2数组与指针
1数组名是数组第一个元素的地址。
数组下标实现的操作指针也可以实现 。
arr+i == &arr[i]
*(arr+i) = arr[i]
int arr[] = {1,2,3,4,5,6,7,8};
for(int i=0;i<8;++i){
printf("%d\n",*(arr+i)); //arr数组首元素首地址 + i等于arr[i]的地址 然后*解引用,最后等于arr[i] 的值
}
2.3函数与数组
2.3.1 传递数组给函数
数组作为函数参数时,通常必须再用一个参数传入数组大小
返回值类型 函数名(类型 参数名[],int size){
}
返回值类型 函数名(类型* 参数名,int size){
}
注意
数组作为参数时,数组退化成指针,不能利用sizeof获取数组大小,也就不能计算数组元素个数。
2.3.2 从函数返回数组
C 语言不允许返回一个完整的数组作为函数的参数。通过指定不带索引的数组名来返回一个指向数组的指针。
return arr//数组名
int arr[] = {1,2,3,4,5,6};
// sizeof(数组名)=类型大小乘数量
printf("sizeof(arr) = %ld\n",sizeof(arr));
// 数组名的值
printf("arr=%p\n&arr[0]=%p\n",arr,&arr[0]);
结果
sizeof(arr) = 24///注意
arr=0x7fff840bef30
&arr[0]=0x7fff840bef30
2.4 多维数组:
多维数组初始化只能第一个维度可以省略。
2.4.1二维数组
2.4.1.1初始化
2.4.1.1.1分行进行初始化
int arr[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
2.4.1.1.2不分行对数组初始化
int arr[2][2] = {1,2,3,4};赋值时即为逐个赋值(先行后列)。
2.4.1.1.3为部分数组元素初始化
static int arrr[2][3] = {{1,2},{3}};第一行只赋值给前两个,第二行只赋值给第一个。这是存储类型是static,故其他未赋值的数组元素的初值为0
2.4.1.1.4省略数组第一维定义,但第二维不可省略。系统会根据给出的初始化数据个数和第二维长度
确定第一维的长度
int arr[][2] = {1,2,3,4}即可等价于 int arr[2][2] = {{1,2},{3,4}}
二维i数组不能整体赋值;
注意 不是所有的C语言系统中存储类型不是static的变量或数组的初值也是0.
2.5const数组
数组变量已经是const指针,表示数组中的每一个元素都是const int,即每个元素不能通过arr改变。
保护数组值
2.5.1作用
因为数组作为函数参数是以地址方式传递的,所以函数内部可以修改数组的值。
为了保护数组不被函数破坏,可设置参数为const。
例如:
int sum(const int arr[],int len);
或者
int sum(const int* arr,int len);
2.6返回值含义
返回值的含义以及值是人为定义的。
返回值有时存在两种情况:合法值和非法值。
如果有非法值的情况,通常使用一些特定的值指代特殊情况。例如:数组下标只能是0和正数。我们实现数组查找元素下标时,存在找不到元素的情况,这时,使用-1可以作为这种情况的返回值。
2.7二维数组指针用法
目标 | 从下标中的值 | 从指针中的值 |
---|---|---|
第i行第j列元素值 | arr[i][j] | ((arr+i)+j) |
第i行第j列元素地址 | &arr[i][j] | *(arr+i)+j |
在二维数组中a[i]就是一个一维数组。
3字符串
3.1. 字符串操作
3.1.1 字符串遍历
1:可以通过数组方式遍历字符串。
char* str="Hello World";
for(int i = 0;'\0' != str[i];++i){
printf("%c\n",str[i]);
}
2:也可以使用指针方式
char* str="Hello World";
for(int i = 0;'\0' != *str;++i){
printf("%c\n",*(str+i));
}
2.1:指针方式可以简化成
char* str="Hello World";
for(;'\0' != *str;++str){
printf("%c\n",*str);
}
2.2:甚至是字符串名
while('\0' != *str){
printf("%c\n",*str++);
}
3.1.2 字符串赋值
char* s = "Hello World";
char* t;
t = s;//没有产生新的字符串,只是s和t指向相同的字符串。下面是查看两个字符串的地址。
printf("%s\n",t);
printf("%p\n",s);
printf("%p\n",t);
结果:
Hello World
0x4007f8
0x4007f8
3.1.3 字符串输入输出
char str[8];
scanf("%s",str);
printf("%s\n",str);
注意:
scanf()读入一个单词直到空白符(空格、回车、Tab)
scanf()不安全,因为不知道要读入的内容长度,容易溢出。解决方式:指定读取的长度。
如:
scanf("%7s",str);
%与s之间的数字表示最多允许输入的字符数,这个数字要比数组长度少1。因为字符数组最后有个结束字符‘/0’
3.2 字符串与函数
1 字符串传参
void print_string(char str[]或者char* str){
printf(str);
}
2.2 字符串返回
字符串返回只能使用指针char*
问题:
字符串传参方式与数组传参方式一样,只不过很多时候不需要传递字符串的长度(为什么?)。因为字符串最后有个自带终止符号 所以可以计算出字符串的大小
‘\0’表示字符串的结束,但不是字符串的一部分。计算字符串长度时不包含’\0’。
3.4字符串指针与字符数组的区别
3.4.1 sizeof与strlen()
char arr[] = "Hello World";
char* ptr = "Hello World";
printf("sizeof(arr) = %ld\n",sizeof(arr));
printf("strlen(arr) = %ld\n",strlen(arr));
printf("sizeof(ptr) = %ld\n",sizeof(ptr));
printf("strlen(ptr) = %ld\n",strlen(ptr));
结果
sizeof(arr) = 12//注意字符数组最后一个字符必须是\0,所以自动补充了一个\0
strlen(arr) = 11//
sizeof(ptr) = 8
strlen(ptr) = 11//'\0'表示字符串的结束,但不是字符串的一部分。计算字符串长度时不包含'\0'。
3.4.2 替换字符
1:修改字符数组
#include <stdio.h>
#include <string.h>
int main(){
char arr[] = "Hello World";
arr[0] = `h`;
arr[6] = `w`;
printf("%s\n",arr);
}
结果:[root@foundation66 c]# ./a.out
hello world
2: 字符串指针
#include <stdio.h>
#include <string.h>
int main(){
char* ptr = "Hello World";;
*ptr = `h`;
*(ptr+6) = `w`;
printf("%s\n",ptr);
}
结果:[root@foundation66 c]# ./a.out
段错误(吐核)
总结:
<1> 初始化的字符串数组存储在栈中,可以改变其值。
<2> 初始化的字符串常量指针存储在静态常量区,不能修改其值。
3:指向字符数组的字符串指针
#include <stdio.h>
#include <string.h>
int main(){
char arr[] = "Hello World";
char* ptr = arr;
*ptr = `h`;
*(ptr+6) = `w`;
printf("%s\n",ptr);
}
结果
[root@foundation66 c]# ./a.out
hello world
4:const字符数组
#include <stdio.h>
#include <string.h>
int main(){
const char arr[] = "Hello World";
arr[0] = 'h';
arr[6] = 'w';
printf("%s\n",arr);
}
结果
zifuchuan.c:5:5: 错误:向只读位置‘arr[0]’赋值
arr[0] = 'h';
^
zifuchuan.c:6:5: 错误:向只读位置‘arr[6]’赋值
arr[6] = 'w';
5:指向const字符数组的字符串指针
#include <stdio.h>
#include <string.h>
int main(){
const char arr[] = "Hello World";
char* ptr = arr;
*ptr = 'h';
*(ptr+6) = 'w';
printf("%s\n",ptr);
}
结果:
zifuchuan.c:5:17: 警告:initialization discards ‘const’ qualifier from pointer target type [默认启用]
char* ptr = arr;
^
[root@foundation66 c]# ./a.out
hello world
总结:注意:决定能否修改的是指针指向的值能否修改。const的限制只针对定义为const的变量。
3.4.3 字符串字面量初始化
如果需要构造字符串使用数组;如果需要处理字符串使用指针。
字符串不需要修改使用字符串字面量初始化字符串指针。
字符串需要修改使用字符串字面量初始化字符数组。
3.5字符串函数
3.5.1 字符串长度
size_t strlen(const char *s);//返回字符串长度不包含\0。
3.5.2 字符串比较
int strcmp(const char *s1,const char *s2);
返回0,表示s1 == s2
返回>0,表示s1 > s2
返回<0,表示s1 < s2
为什么字符串不能直接比较?
为什么字符串比较会有大小?
3.5.3 字符串拷贝
char* strcpy(char* restrict dst,const char* restrict src);//把字符换src拷贝到dst。restrict是C99关键字,表示src和dst不能重叠,便于并行处理。返回值为dst,便于连接。连续赋值。
复制一个字符串
// char* dst = (char*)malloc(strlen(src)+1);
char dst[strlen(src)+1];
strcpy(dst,src);
3.5.4 字符串连接
char* strcat(char* restrict s1,const char*restrict s2);//把s2拷贝到s1的后面,拼接成一个长的字符串。返回s1,
注意:s1必须有足够的空间。
char* a="Hello";
char* b="World";
char res[strlen(a)+strlen(b)+1] = {0};
strcat(strcat(res,a),b);
strcpy和strcat都会有安全问题:dst空间不足,出现越界。
3.5.5 字符查找
char* strchr(const char*s,int c);
char* strrchr(const char*s,int c);//返回找到字符的指针,没找到返回NULL
如何查找第二个?
3.5.6 子串查找
char* strstr(const char*s1,const char*s2);
char* strcasestr(const char*s1,const char*s2);
3.7字符串数组与字符串指针数组
3.7.1字符串数组
字符串数组,可以看成二维字符数组,只是初始化可以使用字符串方式。
char arr[12][10] = {"January","February","March","April",
"May","June","July","August",
"September","October","November","December"};
char arr[][10] = {"January","February","March","April",
"May","June","July","August",
"September","October","November","December"};
3.7.2字符串指针数组
char* arr[12] = {"January","February","March","April",
"May","June","July","August",
"September","October","November","December"}
char* arr[] = {"January","February","March","April",
"May","June","July","August",
"September","October","November","December"};
3.7.3 字符串数组与字符串指针数组的区别
3.7.3.1大小的区别
char arr1[][10] = {"January","February","March","April",
"May","June","July","August",
"September","October","November","December"};
char* arr2[] = {"January","February","March","April",
"May","June","July","August",
"September","October","November","December"};
printf("sizeof(arr1)=%d\n",sizeof(arr1));
printf("sizeof(arr2)=%d\n",sizeof(arr2));
结果:
sizeof(arr1)=120
sizeof(arr2)=96
3.7.3.1二维指针的区别
#include <stdio.h>
int main () {
char arr1[][10] = {"January","February","March","April",
"May","June","July","August",
"September","October","November","December"};
char* arr2[] = {"January","February","March","April",
"May","June","July","August",
"September","October","November","December"};
char** p1 = arr1;
for(int i=0;i<12;++i){
printf("%s\n",p1[i]);
}
char** p2 = arr2;
for(int i=0;i<12;++i){
printf("%s\n",p2[i]);
}
return 0;
}
3.9. main()参数与返回值
3.9.1参数
int main(int argc,char* argv[])
或者
int main(int argc,char** argv)
argc是命令与参数数量。
argv是命令和参数组成的字符串数组。argv[0]是命令本身。其余是参数。
int main(int argc,char* argv[]){
for(int i=0;i<argc;++i){
printf("%d:%s",i,argv[i]);
}
return 0;
}
3.9.2 main()返回值
main()返回值是与调用程序交互的,返回程序执行状态。通常0表示执行成功,非零表示执行失败。
int main(){
int n;
printf("请输入一个整数:");
scanf("%d",&n);
return n;
}
在终端执行程序后,接着执行echo $?可以看到返回值。
3.10. 扩展
char*一定是字符串吗?
char*不一定是字符串,只有以0结尾的字符数组才是字符串。??那为什么赋值时没有零
10.1 0、’\0’与
'\0'表示字符串的结束,但不是字符串的一部分。计算字符串长度时不包含'\0'。
字符串以数组方式存储,可以用数组或者指针形式访问。
3.10.2 空字符串
char str[10]="";//这是一个空字符串,str[0]为\0。
char str[]="";//这是也是一个空字符串,str数组长度为1。
常见错误:使用未初始化的char*
char* str;
printf("%s\n",str);//同常如果指针定义时无法确定初始值时,使用NULL初始化指针。
3.10.3 字符串常量连接
char greeting = "Hello" "World";//两个相邻字符串常量会自动连接。
4进制
C语言不能直接表示二进制常量。八进制数字以0开头,十六进制数字以0x或0X开头。
int n;
scanf("%o",&n);//7
printf("%d\n",n);//7
scanf("%x",&n);//b
printf("%d\n",n);//11
scanf("%d",&n);//12
printf("%d\n",n);//12
//????????
scanf("%i",&n);//7
printf("%d\n",n);//7
5指针
5.1. 指针运算
5.1.1 算术运算
指针与整数相加:表示指针指向下个变量。
指针与整数相减:表示指针指向上个变量。
指针与指针相减:两个指针的元素间隔个数。
int arr[]={100,101,102,103,104,105};
int* p = arr;
int* q;
for(int i=0;i<5;++i){
q = p+i;
printf("%d\n",*q);
}
for(int i=0;i<5;++i){
p = q-i;
printf("%d\n",*p);
}
printf("q-p=%d\n",q-p);结果:q-p=4
自增自减
自增自减 | 等于 |
---|---|
*q++ | *(q++) |
*q- - | *(q- -) |
*++q | *(++q) |
*- -q | *(–q) |
++*q | ++(*q) |
–*q | –(*q) |
如果一个表达式里有多个运算符,则先进行优先级比较,先执行优先级高的运算符;如果优先级相同,那就看结合性,根据结合方向来做运算。
问题
指针与指针可以相加吗?答:两个相同类型的指针不能直接相加的原因是编译器里面不允许出现这种操作。
5.1.2 比较运算符
==、!=、<、<=、>、>=(本质是比较内存中的地址。)
5.1.3 单位长度
指针的加1减1,地址并非加1减1。
int iarr[] = {1,2,3,4,5,6};
int* p = iarr;
for(int i=0;i<5;++i){
printf("%p\n",p++);
}
char carr[] = {1,2,3,4,5,6};
char* q=carr;
for(int i=0;i<5;++i){
printf("%p\n",p++);
}
结果:
p=0x7ffc96d4a330
p=0x7ffc96d4a334
p=0x7ffc96d4a338
p=0x7ffc96d4a33c
p=0x7ffc96d4a340
q=0x7ffc96d4a344
q=0x7ffc96d4a348
q=0x7ffc96d4a34c
q=0x7ffc96d4a350
q=0x7ffc96d4a354
应用范围
指针的算术运算表示在一片连续空间上的移动。
指针的比较运算也是用于一片连续空间的地址比较。
常用于数组等连续内存。
5.2. 指针类型
1:无论指向什么类型,所有指针的大小都是一样的,都是地址的大小。
2:指向不同类型的指针不能直接相互赋值(特例void*),需要强制类型转换
3:指针类型转换没有改变指针内的地址,也没有改变指针指向的值,只是改变了移动的单位长度。
4:void类型的指针:void*是一种很特别的指针,表示指向未知类型的指针,并不指定它是指向哪一种类型的数据,而是根据需要转换为所需数据类型。
5.3指针作用小结
较大数据结构体传入时做参数。
传入数组后,对数组做操作。
函数需要多个返回值时,作为返回值参数。
动态申请内存。
避免使用未初始化指针、空指针和野指针。
5.4 数组指针 , 指针数组,常量指针 , 指针常量,常量指针常量
数组指针 :指向一个数组指针称为数组指针。
指针数组::指针是一个类型,也可以组成一个数组,这样的数组称为指针数组。
常量指针:const int *p
指针常量 :int * const p
常量指针常量:const int * const p
No. | 例子 | 名称 | 指向的值 | 地址 |
---|---|---|---|---|
1 | const int *p/int const *p | 常量指针 | 不可改变 | 可改变 |
2 | int* const p | 指针常量 | 可改变 | 不可改变 |
3 | const int * const p | 常量指针常量 | 不可改变 | 不可改变 |
5.5 0地址
0地址是内存中不能访问的地址。在C语言中,标准库定义NULL表示0地址。
通常用来表示如下:1:指针没有初始化 2:返回指针无效
#include <stdio.h>
int main(){
int *p = 0;
printf("%d\n",*p);
}
6 函数
6.1指针与函数
6.1.1 函数名
函数名与数组名一样表示的是地址,不同的是函数名是执行函数代码的起始位置,数组是数组第一个元素的地址。所以直接使用函数名func和取函数地址&func获取的值是相同的。
6.1.2 函数指针
函数指针是指向函数的指针变量,即本质是一个指针变量。
类型说明符 (*函数名) (参数)
void (*fptr)();
赋值:
fptr = func;//数组名即数组的指针,函数名也是函数的指针。
fptr = &func;
注意:函数名即函数地址。这两种赋值方式完全一样。
调用:
(*fptr)();
fptr();//这两种调用方式完全一样
函数指针就可以作为函数的参数,这称为回调函数。
使用回调函数与使用普通函数不同,使用正常函数,我们可以确定传入的参数,但是使用回调函数是调用函数传入参数。
7结构体
7.1结构体操作
struct Point3D{
int x;
int y;
int z;
};
取地址:
struct Point3D p = {1,2,3};
printf("&p = %p\n",&p);
结构体指针:
struct Point3D* q = &p;
printf("(%d,%d,%d)",q->x,q->y,q->z); // 等同于printf("(%d,%d,%d)",(*q).x,(*q).y,(*q).z);
7.2结构数组
struct Point3D ps[] = {{1,2,3},{1,1,1},{0,0,0}};
for(int i=0;i<3;++i){
printf("(%d,%d,%d)\n",ps[i].x,ps[i].y,ps[i].z);
}
7.3结构体嵌套
struct Line{
struct Point3D start;
struct Point3D end;
};
struct Line line = {{1,1,1},{0,0,0}};
结构体含有结构体数组:
struct Triangle{
struct Point3D p[3];
};
struct Triangle t = {{{1,2,3},{1,1,1},{0,0,0}}};
8.联合体
union 联合体类型名 {
成员
};
1:用法与struct一样。不同点是所有成员公用相同的内存空间。
2:sizeof(联合体)是成员sizeof(成员)中最大的值。
union Test{
double n;
double b;
int a;
};
printf("%ld\n",sizeof(union Test));
结果:8
按从低到高的顺序打印出int类型每个字节的对应的数值(十六进制)
union IntByte{
int n;
char c[sizeof(int)];
};
union IntByte b;
b.n = 1234;
int i;
printf("%08X\n",b.n);
for(i=0;i<sizeof(int);++i){
printf("%02hhX",b.c[i]);
}
printf("\n");
结果
000004D2
D2
04
00
00
9.枚举
9.1. 常量符号化
程序中的数字有时含义不明,被称为魔术数字。通常使用符号来表示。
常用的方式有解决这种问题
const: const double PI = 3.1415926;
define: #define PI 3.1415926
const与#define区别:
No | 比较方向 | #define | const |
---|---|---|---|
1 | 编译处理 | 预处理阶段 | 编译、运行阶段 |
2 | 工作原理 | 简单的字符串替换 | 有对应的数据类型 |
3 | 存储方式 | 展开,在内存中有若干个备份 | 只读变量在内存中只有一份 |
4 | 类型检查 | 没有类型安全检查 | 在编译阶段进行类型检查 |
5 | 作用域 | 从定义开始,任何位置都可访问 | 只能在变量作用域内 |
9.2枚举
枚举是一种用户定义的数据类型
enum 枚举类型名{名字0,名字1,名字2,...,名字n};//枚举大括号里面的名字是常量符号,类型为int,值依次从0到n。枚举就是给这些常量值
,规定一个名字。
enum Mouth{Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sept,Oct,Nov,Dec};
void main(){
enum Mouth m = Jan;//枚举量可以直接作为值使用。
PrintMonth(m);//枚举类型可以直接作为类型使用。
}
// 常用进制
enum Radix{Bin=2,Oct=8,Dec=10,Hex=16};//声明枚举时可以指定值
enum Mouth{Jan=1,Feb,Mar,Apr,May,Jun,Jul,Aug,Sept,Oct,Nov,Dec};//也可以其中一个值,后续值依次加1
10.类型重命名typedef
typedef: 给一个已有的数据类型声明一个新名字。新名字是数据类型的别名。
基本类型重命名
typedef 类型 新名字;
ypedef unsigned char Byte;
Byte b = 0x11;
typedef char* Str;
Str str = "ABCDEFG";
结构体/联合体类型重命名
:我们使用结构体类型时,需要使用struct关键字。typedef可以省略这个关键字。
typedef struct Point3D{
int x;
int y;
int z;
} Point3D;
Point3D p = {1,2,3};
typedef struct{//有时结构体的类型名可以省略
int x;
int y;
int z;
} Point3D;
typedef struct{//定义结构体指针。
int x;
int y;
int z;
} Point3D,*pPoint3D;
Point3D p = {1,2,3};
pPoint3D q = &p;
函数指针类型重命名
语法
typedef 返回类型 (* 函数指针类型)(参数)
实例
int add(int a,int b){return a+b;}
typedef int (*opt)(int,int); // 定义函数指针类型
opt fpadd = &add; // 定义函数指针并赋值
printf("%d\n",(*fpadd)(1,3));
操作 | 语法 | 实例 |
---|---|---|
定义函数 | 返回值类型 函数名(参数类表) | void func(int n){} |
定义函数指针 | 返回值类型 (*函数指针名)(参数类表) | void (*pfunc)(int);pfunc=&func; |
定义函数指针类型 | typedef 返回值类型 (*函数指针类型)(参数类表) | typedef void (*func_t)(int);func_t pfunc=&func; |