c语言笔记
一、警告与报错
出现三个警告:1. 转义字符\可以在字符串中打印双引号,但如果要打印%,则需要%%,
-
multi-line comment:多行注释
在C/C++语言中,在对源文件做预处理的时候,有两条基本原则:
1、凡是以//开头的为单行注释
2、凡是以\结尾的代表此行尚未结束于是预处理器在处理的时候会先按第二条规则,看每行的末尾的那个字符是不是”\”,是的
话,就下一行接到本行。
所以,当单行注释的最后一个字符是,会将下一行也认为是注释,当最后一个字符是汉字时,也可能会因为字符集编码的问题,从而推掉下行代码
-
当一个变量在未被赋值时,参与运算,则或报错
-
initialization of ‘int *’ from ‘int’ makes pointer from integer without a cast(强制转换):
初始化是把一个指针类型的对象赋给一个整形变量,而且也没有进行强制类型转换
-
warning: ‘d’ defined but not used :定义未使用。
-
‘weight’ is used uninitialized (初始化)in this function ,即主要是因为指针没有指向可用的内存地址或没有分配地址
内存泄漏
#include <stdio.h>
#include<string.h>
#define DENSITY 62.4
int main()
{
float weight, volume;
int size, letters;
char name[40];
printf("hi! what\"s your first"); //打印%用%%,打印其他的特殊字符,用
scanf("%s",name); //使用scanf将字符串读入字符数组时不加寻址号,读取基本数据类型时用&
scanf("%f",&weight)
size = sizeof(name); //sizeof后面是变量时,可不加括号
letters = strlen(name);
volume = weight / DENSITY;
printf("well, %s, your volume is %2.2f cubic feet.\n",
name, volume);
printf("also, your first name has %d letter,\n", letters);
printf("and we have %d bytes to store it.\n", size);
return 0;
}
二、sizeof函数
#include <stdio.h>
int main()
{
char a = 'a';
printf("%d\n",sizeof a );
printf("%d\n",sizeof(char));
printf("%d\n",sizeof 'a');
return 0;
}
输出为:1 1 4
分析:a是一个变量,占一个字节,但’a’的本质是:C99标准的规定,'a’叫做整型字符常量(integer character constant),被看成是int型,所以在32位机器上占4字节。
三、格式化输出
#include <stdio.h>
int main(void)
{
printf("%x %X %#x\n",31 ,31 ,31);
printf("**%d**% d**% d**\n", 42,42,42);
printf("**%5d**%5.3d**%05d**%05.3d**\n", 6, 6, 6, 6);
return 0;
}
输出为:1f 1F 0x1f
** 42** 42** 42**
** 6** 006 ** 00006 ** 006**
-
printf格式化输出
#include<stdio.h> int main() { printf("%-5.2f**\n", 123.325); printf("%4f\n", 123.36); printf("%4.2f\n", 123.36); printf("%14f\n", 123.36); printf("%4d\n", 123.36); printf("%d\n", 123.36); return 0; }
输出:
main.c:146:12: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘double’ [-Wformat=]
printf("%d\n", 123.36);
123.33**
123.360000(数据宽度大于格式化的宽度,按实际宽度输出)
123.36(精度小于实际精度,按四舍五入输出)
123.360000(小数点也站一个宽度)
1030792151
1030792151 -
警告: ‘0’ flag ignored with precision and ‘%d’ gnu_printf format
四、三元运算符
三元运算符:?:
-
条件 ? 表达式1 : 表达式2
-
在计算完条件之后,有一个序列点。如果结果不等于 0(换句话说,如果条件计算结果为 true),则只有第二个操作数(也就是表达式 1)会被计算,并且表达式 1 的值就是整个表达式的结果。
另一方面,如果结果为 0(如果条件计算结果为 false),那么只有第三个操作数(也就是表达式 2)会被计算,并且表达式 2 的值就是整个表达式的结果。以这种方式,条件运算符代表了在程序流中的条件式跳转,因此,有时候可以与 if-else 语句相互替代。
inline int iMax(int a, int b) { return a >= b ? a : b; }
- 判断运算符可嵌套使用,?abc?abc:abc
五、分支与跳转
switch语句
switch中必须要是字符或整型 与case后的相比较看是否相等,如果所有的都不符合,就执行defalut语句,或者在没有defalut语句的情况下就直接跳出switch语句,而不输出任何值。
若defalut在程序最前面,则依顺序执行原则,若此时default后没有break,会执行case
#include <stdio.h>
char *p(char c);
char *p(char c)
{
switch(c)
{
case('A') :
return "APPLE";//返回一个指针
case('B'):
return "banana";
default:
return "None";
}
}
int main()
{
char input;
scanf("%c", &input);
printf("%s", p(input));
// printf("%p",&p(input));
printf("%p", &input);
return 0;
} 1
if else语句
六、声明与定义
extern
C++程序通常由许多文件组成,为了让多个文件访问相同的变量,C++区分了声明和定义。
变量的定义(definition)用于为变量分配存储空间,还可以为变量指定初始值。在程序中,变量有且仅有一个定义。
声明(declaration)用于向程序表明变量的类型和名字。定义也是声明:当定义变量的时候我们声明了它的类型和名字。可以通过使用extern声明变量名而不定义它。不定义变量的声明包括对象名、对象类型和对象类型前的关键字extern。
extern声明不是定义,也不分配存储空间。事实上它只是说明变量定义在程序的其他地方。程序中变量可以声明多次,但只能定义一次。
只有当声明也是定义时,声明才可以有初始化式,因为只有定义才分配存储空间。初始化式必须要有存储空间来进行初始化。如果声明有初始化式,那么它可被当作是定义,即使声明标记为extern。
任何在多文件中使用的变量都需要有与定义分离的声明。在这种情况下,一个文件含有变量的定义,使用该变量的其他文件则包含该变量的声明(而不是定义)。
typedef
C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。下面的实例为单字节数字定义了一个术语 BYTE
typedef unsigned char BYTE;
按照惯例,定义时会大写字母,以便提醒用户类型名称是一个象征性的缩写
您也可以使用 typedef 来为用户自定义的数据类型取一个新的名字。例如,您可以对结构体使用 typedef 来定义一个新的数据类型名字,然后使用这个新的数据类型来直接定义结构变量,如下:
#include <stdio.h>
#include <string.h>
typedef struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} Book;
int main( )
{
Book book;
strcpy( book.title, "C 教程");
strcpy( book.author, "Runoob");
strcpy( book.subject, "编程语言");
book.book_id = 12345;
printf( "书标题 : %s\n", book.title);
printf( "书作者 : %s\n", book.author);
printf( "书类目 : %s\n", book.subject);
printf( "书 ID : %d\n", book.book_id);
return 0;
}
#define
#define 是 C 指令,用于为各种数据类型定义别名
- typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
- typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。
七、getchar语句
#include <stdio.h>
int main()
{
int count = 0;
while (getchar() != '\n')
{
count = count + 1;
}
printf("%d", count);
return 0;
}
只能反复输入,而回车操作不能使程序跳出循环,再次运行时可以正常输出。
ps:1. 循环的基本结构:初始化计数器,循环条件,更改计数器
- 素数:不能被2—sqrt(i)这个区间的数整除的,即为素数
八、有关循环的一些注意事项
-
(3) for语句循环中表达式 3:调节器
调节器(例如计数器自增*)在每轮循环结束后且表达式 2 计算前执行*。即,在运行了调节器后,执行表达式 2,以进行判断。 -
for循环中,表达式可以省略,但是;不可以省——for(;;)
-
continue对于for语句与while语句具有不同的影响。
对于 for 循环,continue 语句执行后自增语句仍然会执行。对于 while 和 do…while 循环,continue 语句重新执行条件判断语句。
#include <stdio.h> int main() { for (i = 0; i < 100; i++) { if (i%2) { contiue; } } return 0; }
下面的语句是一个死循环
#include <stdio.h>
int main()
{
int i = 0;
while (i < 100)
{
if (i%2)
{
contiue
}
i++; //被跳过
}
return 0;
}
九、数组的相关知识
-
声明:type arrayName [ arraySize ];
-
初始化:***double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0}或***double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0}***;或***balance[4] = 50.0;
-
数组支持动态定义数组:可以在定义长度时使用变量。
//使用malloc动态定义数组 int n; printf("请输入总人数:\n"); scanf("%d",&n); int *a; a = (int *)malloc(sizeof(int)*n); //使用变量动态定义数组(报错) int i; int n; printf("请输入总人数:\n"); scanf("%d",&n); int a[n]; for(i = 0;i = n;i++) { a[i] = 1; }
-
初始化字符数组的方式
-
字符串的赋值可以用=吗?
答:不可以,报错assignment to expression with array type
翻译:数组类型匹配错误。
给char数组赋值字符串在数组定义时可以完美运行,但是在如上情况就会报错。因为此时数组名表示的是一个指针,指向数组首元素地址,这样赋值就等于尝试修改地址。 -
#include<stdio.h> int main() { char str[5] = { 'A','B','\0','D' }; int i; for (i = 0; i < 5; i++) printf("%d ", str[i]);// \0在内存中实质是0 printf("\n%s", str); printf("\n%s", &str[1]);//传入一个地址 return 0; }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yWTLjBoz-1625412652077)(C:\Users\onlooker\AppData\Roaming\Typora\typora-user-images\image-20210514084755004.png)]
十、相关板块函数的总结
函数
函数声明扩大作用域。
c语言中的形参与实参传递机制和java一样,采用复制传递。
如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。
形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。
当调用函数时,有两种向函数传递参数的方式:
调用类型 | 描述 |
---|---|
传值调用 | 该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。 |
引用调用 | 通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。 |
默认情况下,C 使用传值调用来传递参数。一般来说,这意味着函数内的代码不能改变用于调用函数的实际参数
i/o篇
-
printf
-
scanf:scanf的转换说明的意义:例如%d,你输入一个数,我把他当做10进制
int a; scanf("%o",&a); printf("%d",a); //输入108,结果为8 //原理:在缓冲区存在的108,将尽量去匹配能够用8进制表达的数,所以匹配到10,解读为8
-
putchar
-
getchar
-
puts:属于stdio.h,与printf不同的是:它只显示字符串,且自动在末尾加上换行符(puts(“i always be there for you ”))
字符数组篇<string.h>
- strlen
- strcpy(str1,str2):str2的结束符也会被拷贝过去;且str1的容量大小必须大于str2,否则即使没有报错(c语言不会检查数组的边界),但是会造成在拷贝过程中数据的丢失。
- strncpy(str2,str2,n):n可以控制粘贴过去的长度,但不包括结束符(字符数组中结束符很重要吗?,如果没有,字符串读取时就会继续向后读取,读取到未初始化的内存,可危险了。)
- strcat
- strncat
- strcmp
- strncmp
- 菜鸟教程上全都有
十一、关于指针
#include<stdio.h>
#include<stdlib.h>
void Fun1(int x,int y);
int main()
{
int n = 1,m = 2;
Fun1(n, m);
printf("%d %d", m, n);
}
void Fun1(int x,int y)
{
int temp;
temp = x;
x = y;
y = temp;
}
结果:2 1,交换失败。因为传入的地址在函数结束后消失。
形参与实参是不一样的内存。
其实还真的是,函数调用中的形参在调用时分配内存,调用结束后又被回收。也就是当执行earn(money);的时候,重新开辟了一块跟money变量一样大小的内存,即形参的内存,当函数调用结束后该内存又被回收,相当于过年时候你姑姑给了你压岁钱,然后又被你爸妈拿回去给你姑姑的孩子。earn函数中修改的只是形参的值,而不是实参的值。所以实际上实参的值并没有发生改变,仍然为0
解决办法:传入指针
#include <stdio.h>
void earn(int *money)
{
*money += 100;
}
int main()
{
int money=0;
earn(&money);
printf("I have %d money", money);
return 0;
}
赋值给实参
基础知识
- 指针类型的作用,指明读取的范围。
指向数组的指针
地址常量与地址变量的区别:数据存储的空间中的数据可以被修改,这个空间称为变量,如果空间中的数据不能被修改,这个空间称为常量。地址常量就是地址不能被修改,就像一维数组中的数组名,是一个指针常量,不可被运算和不可被改变。地址变量就是地址能修改,就像一级指针,是一个指针变量,可以通过移动下标或移动指针来改变。
#include <stdio.h>
int main()
{
char *pn = "i lovefishc";
int a[3][4] = {0};
//a[0] + 1跨度4个字节,a + 1跨度16个字节
printf("%p\n",pn);
printf("%s", pn);
int b;
b = &b + 4;//此时b是一个指针
printf("%p\n%p", b,&b);
return 0;
}
输出:
0000000000409000
i lovefishc(使用指针初始化数组,数组名等于指针名)
指向指针的指针
#include<stdio.h>
int main()
{
char *pps[] = {
"c的陷阱与缺陷",
"c专家编程",
"c与指针"};
char **p1;
char **ps[2];
p1 = &pps[0];
ps[0] = &pps[1];
ps[1] = &pps[2];
printf("%s\n", pps[0]);
printf("%s\n", * p1);
printf("%d\n",**p1);
printf("%p\n", *ps[0]);
printf("%d\n", **ps[0]);
return 0;
}
输出:
c的陷阱与缺陷
c的陷阱与缺陷
99
0000000000409016
99
数组指针
当**p = array(一个数组名),编译器只把她当作一个一维数组。p+1上移4个字节。
#include<stdio.h>
int main()
{
char array[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}};
char **pp = array;//会发出警告:initialization of 'char **' from incompatible pointer type 'char (*)[4]
char(*p)[4] = array;//p依旧是指向一维数组,但他的跨度改变了。
printf("%p\n", pp);
printf("%p\n", pp + 1);
printf("%p\n", &array[0]);
printf("%p\n", p);
printf("%d\n", **(p));
printf("%d\n", **(p+1));
printf("%d\n", *(*(p + 1) + 1));
return 0;
}
输出:
000000000061fe14
000000000061fe1c
000000000061fe14s
000000000061fe14
1
5
6
常量与指针
-
指向常量的指针:可以修改为指向不同的变量,可以修改为指向不同的常量,可以读取指针指向的数据,但不可以通过解引用修改指针指向的数据。
-
常量指针:本身不可以改变,但*p可以改变。
-
指向常量的常量指针:*p与p都不可以修改。
#include <stdio.h> int main () { int num = 1,cnum = 2; const int *p = &cnum; const int *const pp = # num = 2; cnum = 3; printf("%d",*pp); printf("%d", *p); return 0; }
输出:2 3
函数指针与指针函数
函数指针:
函数指针是指向函数的指针变量。
通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。
函数指针可以像一般函数一样,用于调用函数、传递参数。
函数指针变量的声明:
typedef int (*fun_ptr)(int,int); // 声明一个指向同样参数、返回值的函数指针类型 #include <stdio.h> int max(int x, int y) { return x > y ? x : y; } int main(void) { /* p 是函数指针 */ int (* p)(int, int) = & max; // &可以省略 int a, b, c, d; printf("请输入三个数字:"); scanf("%d %d %d", & a, & b, & c); /* 与直接调用函数等价,d = max(max(a, b), c) */ d = p(p(a, b), c); printf("最大的数字是: %d\n", d); return 0;
指针函数:以指针为返回值的函数。
返回的指针如果是函数中的内部局部变量的地址,则返回值是一个无效的地址。
十二、逻辑表达式的短路特性
#include <stdio.h>
int main()
{
int a = 1, b = 0, c = -2;
int z;
z = a++ || ++b && --c;
printf("a = %d,b = %d,c = %d,z = %d\n", a, b, c, z);
a = 0;
z = a++||++b&&--c;
printf("a = %d,b = %d,c = %d,z = %d\n", a, b, c, z);
b = -1;
z = a++ || ++b && --c;
printf("a = %d,b = %d,c = %d,z = %d\n", a, b, c, z);
return 0;
}
运算结果:
a = 2,b = 0,c = -2,z = 1
a = 1,b = 1,c = -3,z = 1
a = 2,b = -1,c = -3,z = 1
结果分析:
- a++为2,||为或,在a = 2后,就不对++并进行计算,直接得到结果1;非0的数都是真。
十三、存储类别、链接与内存管理
作用域、链接与存储期
#include<stdio.h>
int func();//全局作用域
int count;//全局
extern int c;
static int d;
int func(int b)//b是局部
{
printf("%d\n", count);
int count = b;
printf("%d", count);
return 0;
}
int main ()
{
int a;
func(1);
printf("\n");
a = count;
printf("%d", a);
return 0;
}
输出:0 1 0
- 在块中定义一个static变量的结果:得到一个静态局部变量,作用域不变,生存期变为静态存储。静态存储:内存空间分配以后,变量一直存在,在块调用完成后,不清零。再调用的时候,等于进行了两次操作。
- extern:全局变量,作用范围覆盖整个文件,但是当同名时,局部变量优先
动态内存管理
malloc
-
函数原型与其具体说明:void *malloc(size_t size),向系统申请size个字节的内存空间,并返回一个指向这个空间的void型指针。
-
void型指针可转化为任意类型的指针
-
调用失败或size设置为0,返回null
#include <stdio.h>
#include<stdlib.h>
int main ()
{
int *p;
p =(int*) malloc(num * sizeof(int));//不同的系统int的字节数不一样
if (p == NULL)
{
printf("调用失败");
}
else
{
printf("请输入一个整数");
scanf("%d\n", p);
printf("%d\n", *p);//为甚么要输入两次才能成功读取呢(scanf后面不能用于读取\n)
//&p[i + 1],
}
return 0;
}
free
- void free(void *ptr)
- 用于释放由malloc、calloc、realloc申请的空间。释放其他的空间将导致未定义行为。空间为null,则不作操作。
- 函数不修改参数本身的值,调用后*ptr被清空,但指针指向原来的地方。
#include <stdio.h>
#include<stdlib.h>
int main ()
{
int *p;
p =(int*) malloc(sizeof(int));
if (p == NULL)
{
printf("调用失败");
}
else
{
printf("请输入一个整数");
scanf("%d\n", p);
printf("%d\n", *p);
free(p); //此处释放了空间
printf("%d\n", *p); //此时访问为非法访问
}
return 0;
}
输出:
2
2(scanf读取两个2,后,正确输出)
9986864
十四、字符串
-
puts函数只显示字符串
#include<stdio.h> int main() { puts("you") return 0; }
定义字符串的方法
1、字符串常量
1. 用“”括起来的叫做字符常量 2. 如果字符串常量之间没有间隔,视为一个常量 3. \“可以打印双引号 3. 字符串常量属于静态存储类别,若在函数中使用,该字符串只被存储一次,并存在于整个函数的生命周期。即使多次调用,用双括号括起来的内容会被视为指向该数组的指针。 4. 定义时,a[]的长度包含了\0的内存,如果填满,输出时将不会停止。 5. puts(“abc\0777\0”),strlen("abc\0777\0"),输出为abc7,5。 6. 字符串中出现\,可能是由1-3位8进制数,2位16进制数构成的转义,此时只构成一个字符。若两种都不是,\将被视为不存在。 4.
十五、结构体
struct tag { //标签的作用
member-list
member-list
member-list
...
} variable-list ;//声明结构体变量
tag 是结构体标签。
member-list 是标准的变量定义,比如 int i; 或者 float f,或者其他有效的变量定义。
variable-list 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。下面是声明 Book 结构的方式:
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct
{
int a;
char b;
double c;
} s1;
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE
{
int a;
char b;
double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
//也可以用typedef创建新类型
typedef struct
{
int a;
char b;
double c;
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;
关于在结构体变量在存储时的内存对齐问题
结构体嵌套:结构体中有另外一个结构体作为成员
#include<stdio.h>
struct Data {
int year;
int month;
int day;
};
struct Book {
int number;
struct Data data;
}book = {
1,
{2020,3,12}
};
int main()
{
printf("%d ", book.data.month);
return 0;
}
结构体数组与结构体指针
#include<stdio.h>
struct Data {
int year;
int month;
int day;
};
struct Book {
int number;
struct Data data;
}book = {
1,
{2020,3,12}
};
int main()
{
struct Book* p;
p = &book;//结构体的名字不是指向首地址的指针,所以要加&
printf("%d ", p->data.month);//->意为对象选择,适用对象是指针,(*p).data = p->data;
printf("% ", (*p).data.month);//.号运算符的优先级大于*,所以要加括号
printf("%d ", book.data.month);
return 0;
传递结构体变量与结构体指针
因为传递结构体变量名,会拉慢程序效率(因为结构体的数据量可能会很庞大,传入结构体变量会将所有的数据全部传入,浪费时间与空间),所以传入指针会使更好的办法。
访问结构体有两种方式:下标或者是点号取值
#include<stdio.h>
struct Data {
int year;
int month;
int day;
}data;
struct Book {
int number;
struct Data data;
}book1;
void get_month(struct Book *book)
{
book->data.month = 3;
}
int main()
{
struct Book *book3 = &book1;
get_month(book3);
printf("%d ", book1.data.month);
return 0;
}