1、前言
大家好,我是Lucas,很高兴能融入社区这个大家庭,希望我们能够一起学习一起加油!我编程的目标是能够成为一名大佬。通过学习与实践相互结合,来增强自己敲代码的能力。我打算每周要学习40+小时,来快速强化我的编程能力,大厂IT公司(百度阿里美团腾讯db字节头条去哪儿25w以上)。
38*3=114
2、什么是C语言
C语言是任何计算机交流的一种计算机语言,广泛应用于底层开发。C语言是一门面向过程的计算机编程语言。
3、数据类型
![](https://i-blog.csdnimg.cn/blog_migrate/b5dcca9823c65d94af22186c2538411a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d7081ee708aa7f43c962e7c3c68cd1bd.png)
4、变量、常量
int age = 150;
float weight = 45.5f;
char ch = 'w';
//55.5 小数默认是double类型的
//55.5f 这个时候就是float类型
- 只能由字母(包括大写和小写)、数字和下划线( _ )组成。
- 不能以数字开头。 长度不能超过63个字符。
- 变量名中区分大小写的。
- 变量名不能使用关键字。
- 变量名要有意义
#include <stdio.h>
int global = 2019;//全局变量
int main()
{
int local = 2018;//局部变量
//下面定义的global会不会有问题?
int global = 2020;//局部变量
printf("global = %d\n", global);
return 0;
}
//上面的局部变量global变量的定义其实没有什么问题的!
//当局部变量和全局变量同名的时候,局部变量优先使用。
//%c - 字符
//%d - 整型
//%s - 字符串
//%f - float
//%lf - double
//%p - 地址的打印
作用域
作用域(scope)是程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用 的而限定这个名字的可用性的代码范围就是这个名字的作用域。
1. 局部变量的作用域是变量所在的局部范围。
2. 全局变量的作用域是整个工程。
//add.c
//全局变量
int g_val = 203
//test.c
//需要声明外部符号
extern int g_val
int main(){
printf("%d",g_val);
return 0;
}
生命周期
变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段
1. 局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。
2. 全局变量的生命周期是:整个程序的生命周期。
C语言中的常量分为以下以下几种:
- 字面常量 ('a'-字符,“asdf”-字符串)
- const 修饰的常变量
- #define 定义的标识符常量
- 枚举常量
#include <stdio.h>
//举例
enum Sex
{
MALE,
FEMALE,
SECRET
};
//括号中的MALE,FEMALE,SECRET是枚举常量
int main()
{
//字面常量演示
3.14;//字面常量
1000;//字面常量
//const 修饰的常变量 具有常属性但本质还是变量
const float pai = 3.14f; //这里的pai是const修饰的常变量
pai = 5.14;//是不能直接修改的!
//#define的标识符常量 演示
#define MAX 100
printf("max = %d\n", MAX);
//枚举常量演示
printf("%d\n", MALE);
printf("%d\n", FEMALE);
printf("%d\n", SECRET);
//注:枚举常量的默认是从0开始,依次向下递增1的
return 0;
}
static修饰变量
1. static修饰变量
a. 函数中局部变量:
声明周期延长:该变量不随函数结束而结束
初始化:只在第一次调用该函数时进行初始化
记忆性:后序调用时,该变量使用前一次函数调用完成之后保存的值
存储位置:不会存储在栈上,放在数据段
b. 全局变量
改变该变量的链接属性,让该变量具有文件作用域,即只能在当前文件中使用
c. 修饰变量时,没有被初始化时会被自动初始化为0
2. static修饰函数
改变该函数的链接属性,让该函数具有文件作用域,即只能在当前文件中使用
5、字符串+转义字符+注释
1)字符串
由双引号(Double Quote)引起来的一串字符称为字符串字面值(String Literal),或者简称字符 串。
注:字符串的结束标志是一个 \0 的转义字符。在计算字符串长度的时候 \0 是结束标志,不算作字符串 内容。
arr2没有用\0结束,所以打印时会一直打印直到遇到\0
char ch1[] = {'a','b'};
int len = strlen(ch1);
//strlen是库函数,用来求字符串长度,就是从给定地址向后数字符,直到遇到\0结束,\0不统计在内
2)转义字符
转义字符 | 释义 |
\? | 在书写连续多个问号时使用,防止他们被解析成三字母词 |
\' | 用于表示字符常量' |
\“ | 用于表示一个字符串内部的双引号 |
\\ | 用于表示一个反斜杠,防止它被解释为一个转义序列符。 |
\a | 警告字符,蜂鸣 |
\b | 退格符 |
\f | 进纸符 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ddd | d d d表示1~3个八进制的数字。 如: \130 表示字符X |
\xdd | d d表示2个十六进制数字。 如: \x30 表示字符0 |
3)注释
1. 代码中有不需要的代码可以直接删除,也可以注释掉
2. 代码中有些代码比较难懂,可以加一下注释文字
注释有两种风格:
- C语言风格的注释 /*xxxxxx*/
缺陷:不能嵌套注释
- C++风格的注释 //xxxxxxxx
可以注释一行也可以注释多行
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
/*C语言风格注释
int Sub(int x, int y)
{
return x-y;
}
*/
int main()
{
//C++注释风格
//int a = 10;
//调用Add函数,完成加法
printf("%d\n", Add(1, 2));
return 0;
}
6、分支和循环
C语句可分为以下五类:
- 表达式语句
- 函数调用语句
- 控制语句
- 复合语句
- 空语句
控制语句用于控制程序的执行流程,以实现程序的各种结构方式(C语言支持三种结构:顺序结构、选 择结构、循环结构),它们由特定的语句定义符组成,C语言有九种控制语句。
可分成以下三类:
- 条件判断语句也叫分支语句:if语句、switch语句;
- 循环执行语句:do while语句、while语句、for语句;
- 转向语句:break语句、goto语句、continue语句、return语句。
1)分支(选择)
#include <stdio.h>
//代码1
int main()
{
int age = 0;
scanf("%d", &age);
if(age<18)
{
printf("未成年\n");
}
}
//代码3
#include <stdio.h>
int main()
{
int age = 0;
scanf("%d", &age);
if(age<18)
{
printf("少年\n");
}
else if(age>=18 && age<30)
{
printf("青年\n");
}
else if(age>=30 && age<50)
{
printf("中年\n");
}
else if(age>=50 && age<80)
{
printf("老年\n");
}
else
{
printf("老寿星\n");
}
}
如果表达式的结果为真,则语句执行。
在C语言中如何表示真假? 0表示假,非0表示真。
#include <stdio.h>
int main()
{
if(表达式)
{
语句列表1;
}
else
{
语句列表2;
}
return 0;
}
2)循环
- while
- for
- do while
7、函数
维基百科中对函数的定义:子程序
- 在计算机科学中,子程序(英语:Subroutine, procedure, function, routine, method, subprogram, callable unit),是一个大型程序中的某部分代码, 由一个或多个语句块组 成。它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。
- 一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软 件库。
1)库函数
我们在开发的过程中每个程序员都可能用的到, 为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员 进行软件开发。
cplusplus.com/reference/https://cplusplus.com/reference/
C语言常用的库函数都有:
- IO函数
- 字符串操作函数
- 字符操作函数
- 内存操作函数
- 时间/日期函数
- 数学函数
- 其他库函数
参考手册:
cppreference.comhttps://zh.cppreference.com/w/%E9%A6%96%E9%A1%B5
2)自定义函数
函数的组成:
ret_type fun_name(para1, * )
{
statement;//语句项
}
ret_type 返回类型
fun_name 函数名
para1 函数参数
3)函数的参数
实际参数(实参):
真实传给函数的参数,叫实参。 实参可以是:常量、变量、表达式、函数等。 无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
形式参数(形参):
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内 存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数 中有效。
形参实例化之后其实相当于实参的一份临时拷贝。
8、数组
一组相同类型元素的集合。
定义:
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };//定义一个整型数组,最多10个元素
//数组10个元素,下标的范围是0~9
9、操作符
1)操作符分类
- 算术操作符
- 移位操作符
- 位操作符
- 赋值操作符
- 单目操作符
- 关系操作符
- 逻辑操作符
- 条件操作符
- 逗号表达式
- 下标引用、函数调用和结构成员
2)算术操作符
+ - * / %
- 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数
- 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
- % 操作符的两个操作数必须为整数。返回的是整除之后的余数。
3)移位操作符
<< 左移操作符
>> 右移操作符
//移位操作符的操作数只能是整数。
4)位操作符
& //按位与
| //按位或
^ //按位异或
//他们的操作数必须是整数。
5)赋值操作符
int weight = 120;//体重
weight = 89;//不满意就赋值
复合赋值符:
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
6)单目操作符
只有一个操作数
- ! 逻辑反操作
- - 负值
- + 正值
- & 取地址
- sizeof 操作数的类型长度(以字节为单位)
- ~ 对一个数的二进制按位取反
- -- 前置、后置--
- ++ 前置、后置++
- * 间接访问操作符(解引用操作符)
- (类型) 强制类型转换
sizeof和数组
#include <stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//(2)
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));//(4)
}
//sizeof是一个操作符
int main()
{
int arr[10] = {0};
char ch[10] = {0};
printf("%d\n", sizeof(arr));//(1)
printf("%d\n", sizeof(ch));//(3)
test1(arr);
test2(ch);
return 0;
}
//(1)、(2)两个地方分别输出多少?
//(3)、(4)两个地方分别输出多少?
//前置++和--
#include <stdio.h>
int main()
{
int a = 10;
int x = ++a;
//先对a进行自增,然后对使用a,也就是表达式的值是a自增之后的值。x为11。
int y = --a;
//先对a进行自减,然后对使用a,也就是表达式的值是a自减之后的值。y为10;
return 0;
}
//后置++和--
#include <stdio.h>
int main()
{
int a = 10;
int x = a++;
//先对a先使用,再增加,这样x的值是10;之后a变成11;
int y = a--;
//先对a先使用,再自减,这样y的值是11;之后a变成10;
return 0;
}
7)关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
8)逻辑操作符
& 逻辑与
|| 逻辑或
区分逻辑与和按位与
区分逻辑或和按位或
1&2----->0
1&&2---->1
1|2----->3
1||2---->1
9)条件操作符
exp1 ? exp2 : exp3 //三目操作符
真 执行 不执行
假 不执行 执行
m = ( a > b ? a , b )
10)逗号表达式
exp1, exp2, exp3, …expN
逗号表达式,就是用逗号隔开的多个表达式。 逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
//代码1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
c是多少?
//代码2
if (a =b + 1, c=a / 2, d > 0)
//代码3
a = get_val();
count_val(a);
while (a > 0)
{
//业务处理
a = get_val();
count_val(a);
}
如果使用逗号表达式,改写:
while (a = get_val(), count_val(a), a>0)
{
//业务处理
}
11)下标引用、函数调用和结构成员
1. [ ] 下标引用操作符 操作数:一个数组名 + 一个索引值
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
[ ]的两个操作数是arr和9。
2. ( ) 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
#include <stdio.h>
void test1()
{
printf("hehe\n");
}
void test2(const char *str)
{
printf("%s\n", str);
}
int main()
{
test1(); //实用()作为函数调用操作符。
test2("hello bit.");//实用()作为函数调用操作符。
return 0;
}
3. 访问一个结构的成员
. 结构体.成员名
-> 结构体指针->成员名
10、常见操作符
auto,break,case,char,const,continue,default,do,double,else,enum,sxtern,float,for,goto,if,int,long,register,return,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile,while。
这些关键字都是语言预先设计好的,用户自己是不能创造关键字的
自动 | 循环 | 分支 | 数据类型 | 修饰类型 | 自定义类型 |
auto | break continue do while for while | break switch case dfault 默认 if else go to | char double float int long short | const signed 有符号的 unsigned 无符号的 typedef 类型重定义 | enum 枚举类型 struct 结构体类型 union 联合体类型 |
声明外部符号 | 寄存器 | 函数的返回 | 计算变量类型大小 | 函数返回、参数,修饰指针 | |
extern | register | return | sizeof | void |
关键字typedef
typedef struct Node {
int data;
struct Node* next;
}Node;
关键字static
static是用来修饰变量和函数的
- 修饰局部变量,称为静态局部变量
- 修饰全局变量,称为静态全局变量
- 修饰函数,称为静态函数
//static.c
int g_val = 232;//全局变量,可外部链接
static int g_val = 232;//不可外部链接
//operator.c
//全局变量具有外部链接属性
//static修饰全局变量,改变了这个全局变量的链接属性,由外部链接属性变成了内部链接属性
//声明外部符号
extern int g_val;
int main() {
//static修饰全局变量
printf("%d\n", g_val);
return 0;
}
11、#define定义常量和定义宏
//define定义标识符常量
#define MAX 100
//define定义宏
#define ADD(x,y) ((x)+(y))
int main() {
int a = 10, b = 10;
int c = ADD(a, b);
printf("%d\n", c);
return 0;
}
12、指针
- 指针是内存中一个最小单元的编号,也就是地址
- 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量。
指针变量
我们可以通过&(取地址操作符)取出变量的内存起始地址,把地址可以存放到一个变量中,这个 变量就是指针变量。
int main(){
int a = 10;//在内存中开辟一块空间
int* p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个指针变量。
*p = 30;//解引操作符,*p就是a
printf("%d\n", a);//打印a后a=30
return 0;
}
指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
总结:
- 指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
- 指针的大小在32位平台是4个字节,在64位平台是8个字节。
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
//指针的定义方式是: type + *
1)指针+-整数
#include <stdio.h>
//演示实例
int main()
{
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}
//指针的类型决定了指针向前或者向后走一步有多大(距离)。
2)指针的解引用
#include <stdio.h>
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
int *pi = &n;
*pc = 0; //重点在调试的过程中观察内存的变化。
*pi = 0; //重点在调试的过程中观察内存的变化。
return 0;
}
//指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
//比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
3)野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
1.指针未初始化
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
2.指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
3.指针指向的空间释放
13、结构体
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
typedef struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}Stu;//分号不能丢
结构的成员可以是标量、数组、指针,甚至是其他结构体。
1)结构体定义和初始化
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = { x, y };
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
};
struct Stu s = { "zhangsan", 20 };//初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = { 10, {4,5}, NULL }; //结构体嵌套初始化
struct Node n2 = { 20, {5, 6}, NULL };//结构体嵌套初始化
2)结构体成员的访问
1.结构体变量访问成员
结构变量的成员是通过点操作符(.)访问的。点操作符接受两个操作数。
struct S s;
strcpy(s.name, "zhangsan");//使用.访问name成员
s.age = 20;//使用.访问age成员
2.结构体指针访问指向变量的成员
struct Stu
{
char name[20];
int age;
};
void print(struct Stu* ps)
{
printf("name = %s age = %d\n", (*ps).name, (*ps).age);
//使用结构体指针访问指向对象的成员
printf("name = %s age = %d\n", ps->name, ps->age);
}
int main()
{
struct Stu s = { "zhangsan", 20 };
print(&s);//结构体地址传参
return 0;
}
3.结构体传参
struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
//首选print2函数。
//原因:
//函数传参的时候,参数是需要压栈的。
//如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的
//下降。