初学C语言————小白的学习之路

引言

写在前面,本人是一个小小白,大学三年没有好好学习,到现在开始展望未来,发现是道路堪忧,条条道路通罗马,可是我不是条条,虽然怎么样都是在生活,都能生活,但是为了以后稍微好点,还是开始了学习,不是很虔诚,因为抱着很强的功利心,写这个博客也是当做自己的笔记,主要是听鹏哥的课,讲的很细,如果是想学C的朋友可以去看看,你要学会一个东西,不付出时间很精力是不行的,学习态度端正一下,希望在这段时间的学习中,有所进步!

因为以后的方向是嵌入式方向,他基础是C,所以现在开始。还写了一段很中二的话:

我们知道,真正的英雄主义只有一个,那就是,认清现实后,依旧热爱。

于是,我来了。

第一章:启航

首先我们来看一个简单的例子:



#include <stdio.h>

int main()
{
	printf("Hello,world!");
	return 0;
}

这一步骤,我们写出了我们C语言的第一个代码hello,world!这个小程序很简单,但麻雀虽小五脏俱全,我们来分析一下这个语句

1.#define 这个语句是预处理操作,include是被称为文件包含命令,<>中内容称为头部文件或者

2,主函数main,程序有且只有一个主函数main,他是程序的入口。

3.函数体{}和里面内容就是函数体

4,return 0;这个语句可以理解为函数的结束标志

补充/* */   ,   //  为函数的注释语句,写注释是一个良好习惯

我们这里再来补充一下C语言的格式:

1,主函数main,放什么位置都没有关系

2.C程序主题都是由函数构成的,这些定义函数可以在main里面,但是如果一块一块放外面,看起来易于观察

3,函数体都要在{}里面

4,每一个执行语句都以“;”结尾,英文分号,

5,英文字符大小不通用

6,空格,空行使用

用的编译器msvc

第二章,初识C语言

第一节:编程规范

了解规范让你编写代码具有可读性,通用性,而且美观

1,代码缩进:代码缩进为四个字符。不用空格,用一个Tab

2,变量,常量命名规范:

常量命名统一为大写格式。  #define AGE        20

普通变量取实际意义相关的名称,前面添加类型首字母,名称首字母大写 int iNumber;

指针,在标识符前添加p字符,并且名称首字母大写    int pAge;

3,定义函数时,函数名首字母大写,其后的字母大小写混合

4,尽量采用行注释。

第二节,关键字

C语言有32个关键字,

auto  break   case  char  const   continue  default  do   double else  enum   extern float  for   goto  if   int   long  register    return   short  signed sizeof   static struct  switch  typedef union  unsigned   void  volatile  while

关键字不是自己创建的哦

循环break        for        while        do while        continue        

分支case        switch        defalt        if  else        goto

int        char        short        float        double        long

enum(枚举)        struct(结构体)        union(联合体)        signed(有符号的)unsigned(无的)

extern(声明外部符号)

register(寄存器)        static(静态的)

return (函数返回值)        void(空,函数的返回类型,函数参数)

sizeof(计算大小)

typedef(类型重命名)

volatile(操作系统会用到)

1,typedef 顾名思义是类型定义,这里应该理解为类型重命名。

#include<stdio.h>
typedef unsigned int unit;
int main()
{
	unsigned int a = 0;
	unit b = 0;
	return 0;
}

注意只能对类型

2,关键字static

static是用来修饰变量和函数的

        1. 修饰局部变量-称为静态局部变量

以下为未使用static,输出结果为2222222222

#include <stdio.h>
void test()
{
    int i = 1;
    i++;
    printf("%d ", i);
}
int main()
{
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        test();
    }
    return 0;
}

     使用static,输出结果为234567891011

#include <stdio.h>
void test()
{
    static int i = 1;
    i++;
    printf("%d ", i);
}
int main()
{
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        test();
    }
    return 0;
}

static修饰局部变量改变了变量的生命周期

让静态局部变量出了作用域依然存在,到程序结束,生命周期才结束。

注意:

a.这里有个问题就是a++这个。a++我们记忆很常见是先使用后增加,但是这个是对我们的

int a = 0 ; int b =a++;这样语句就是先使b = 0,后a = 1,然而a++;这样的语句则是直接使用,可以这样理解就是不用也得加加,就是结果a=1. 

b:本质上,static修饰局部变量时,改变了变量的存储位置.

内存被划分为

栈区(存放局部变量):进入作用域则有,出去则被销毁

堆区()

静态区(静态变量):程序结束则销毁

   2. 修饰全局变量-称为静态全局变量

使用这个记得要使用extern 生命一下

全局变量具有外部链接属性

static修饰全局变量的外部链接属性变成内部链接属性,其他源文件则不能使用这个全局变量

        3. 修饰函数-称为静态函数

其实感觉就是用了static之后,你就是跟全局变量一样,只能在一个.c中使用,不能在别的其他使用,但是我不太理解为什么用这个东西,他这几个作用我知道了,但在后面程序中怎么用呢?等后 我知道了补充吧

这里再来一个小科普吧:我们电脑中的缓存原件都有那些?

寄存器  高数缓存器  内存   硬盘   

他们内存大小越来越大,造价越来越低,速度越来越慢

所以写代码时候可以用register  int   num = 3 //建议3放在寄存器中

第三节,标识符

在C语言中为了可以使用变量,常量,函数,数组等,就要为他们设置一个名称,这些设置的名称就是标识符,这些你可以随便命名,但是必须遵守一定的规则,

1,必须由字母或者下划线开头,其他都不可

2,除开头位置,其他位置可以由数字,字母,下划线组成

3,英文字母大小写代表的标识符不一样;      A和a是两个标识符

4,标识符不能是关键字

5,有一定意义,自己别人能看懂

6,格式说明符号

%d整型输出,%ld长整型输出,

%o以八进制数形式输出整数,

%x以十六进制数形式输出整数,或输出字符串的地址。

%c用来输出一个字符,

%s用来输出一个字符串,

%f用来输出实数,以小数形式输出,默认情况下保留小数点6位。

%p用来输入指针,

第四节  变量和常量

变量,:变化的量

常量:不变的量

#define定义常量和宏

1,定义常量   define   MAX 1000

2.define定义宏,宏是有参数

比方说定义两个数相加可以写成 define ADD(x,y)   ((x)+(y))

ADD宏名 (x,y)宏的参数  ,参数无类型,就是一个符号  ((x)+(y))宏体

//define定义宏
#define ADD(x, y) ((x)+(y))
#include <stdio.h>
int main()
{
    int sum = ADD(2, 3);
    printf("sum = %d\n", sum);

    sum = 10 * ADD(2, 3);
    printf("sum = %d\n", sum);

    return 0;
}

第五节  数据类型   (重点)

1,基本类型

整型 int  : 短整型      基本整型      长整型

字符型

实型(浮点型):单精度型       双精度型

枚举类型

2,构造类型

数组

结构体

共用体

3,指针类型

4,空类型

第六节,操作符

1,基本的符号

"/"他意思是除号,下述代码中.3f则是表达取小数点的后三位

"%"他意思则是取余数,注意//取余数的两端操作符只能是整数

#include<stdio.h>
int main()
{
	
	float a = 9.3/21;
	printf("%.3f\n", a);
	int b = 8 % 3;//取余数的两端操作符只能是整数
	printf("%d\n", b);
	return 0;
}

加减乘+  -  *,则和数学类似不多说,注意*不能省略即可

2,移位操作符,

<<左移操作符       >>右移操作符

这个暂时不写,老师说后面讲,涉及二进制的运算

3.位操作符,

&……^.......|     放后面讲

4.赋值操作符

= 赋值:a = 20这就赋值

a=a+3    等同于  a+=3

+=  -=  *=  /=   %=   >>=   <<=    &=    |=     ^= 这些运算符都可以写成复合的效果。

5,单目操作符(知道一下,能看懂)

:只有一个操作数的操作符

             !           逻辑反操作

        /c语言中0表示假,!则是取反的意思,逻辑反操作

        -           负值

        int a = 10;
        int b = -a;

        +           正值,这个没有啥意思

        &           取地址,这个跟指针有关系,等到我们后面再讲这个东西

        sizeof      操作数的类型长度(以字节为单位),这个也是操作符

        printf("%d\n", sizeof a);
        printf("%d\n", sizeof(int));

这是他的简单用法,注意标识符可以去掉括号,但是类型不能去括号

        int arr[10] = { 0 };
         printf("%d\n", sizeof(arr));//计算整个列表的大小,单位是字节,应该是40
        printf("%d\n", sizeof(arr[0]));//列表中一个的字节是多少  4
        printf("%d\n", sizeof(arr)/sizeof(arr[0]));//列表的元素个数

        ~           对一个数的二进制按位取反,先不讲

        --          前置、后置          前置、后置++

        i++后置,先用后加

             int a = 10;
             int b = a++;
            //int b =a ,a=a+1;
            printf("%d\n", b);//10
            printf("%d\n", a);//11

        ++i,它则是先加1 ,然后赋值,减减同理

        *           间接访问操作符(解引用操作符)后面介绍

        (类型)       强制类型转换

        int A = (int)3.90;

         printf("%d\n", A);

6. 关系操作符

> >=

< <=

!=   用于测试“不相等”

==      用于测试“相等

7. 逻辑操作符

&&     逻辑与  并且,都真为真,学过数电都知道,全1为1 ,有0为0

||          逻辑或    或者         有1为1 ,全0为0 

#include<stdio.h>
int main()
{
int a = 0;
	int b = 10;
	if (a||b)//条件非0为真,上面有写过,这个为逻辑或
	{
		printf("呵呵");
	}
	return 0;
}

8. 条件操作符(三目操作数)

exp1 ? exp2 : exp3

如果1为真,表达式2算,表达式3不算,假则为2不算,3算

int a = 100;
    int b = 10;
    int r = a > b? a : b;
    printf("%d\n", r);

这个可以用来求两数最大值


9. 逗号表达式

逗号表达式,就是用逗号隔开的多个表达式。

逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果

int a = 1;
    int b = 2;
    int c = (a > b, a = b + 10, a, b = a + 1);
    printf("%d\n", c);  //结果c = 13

10,下标引用、函数调用和结构成员

1,下标引用

         [ ] 下标引用操作符 操作数:一个数组名 + 一个索引值

int arr[10];//创建数组

arr[9] = 10;//实用下标引用操作符。 [ ]的两个操作数是arr和9。

2,函数调用

        ( ) 函数调用操作符 接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。

第七节 函数

#define  _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>
int Add(int x, int y)
{
    int z = x + y;
    return z;
}
int main()
{
    int num1 = 0;
    int num2 = 0;
    int sum = 0;
    printf("输入两个操作数:>");
    scanf("%d %d", &num1, &num2);
    sum = Add(num1, num2);
    printf("sum = %d\n", sum);
    return 0;
}

后面细讲

第八节  数组

要存储1-10的数字,怎么存储? C语言中给了数组的定义:一组相同类型元素的集合

int arr[10] = {1,2,3,4,5,6,7,8,9,10};//定义一个整形数组,最多放10个元素

C语言规定:数组的每个元素都有一个下标,下标是从0开始的。 数组可以通过下标来访问的。 比如:

int arr[10] = {0}; //如果数组10个元素,下标的范围是0-9

第九节,指针

  这里我们要先了解内存这个概念

总所周知,bit 是最小的单位  目前生活相关的1TB = 1024GB ,1GB =1024 MB ,1MB = 1028KB ,1KB = 1024B ,1B  = bit

编号  地址     地址也被称为指针

我们可以简单写一个代码来实践一下

#include<stdio.h>
int main()
{
	int a = 10;//向内存申请四个地址,存储10
	&a;//取a地址操作符
	printf("%p", &a);
	return 0;
}

F10运行代码,在依次选择 调试  窗口    监视  和 内存,我们就可以知道这个地址的位置

也就是说:

指针就是地址!(指针就是用来)

而指针变量就是存储地址的一个变量!

int a = 10;

int* p = &a;

*p = 20;

这里面的变量是p,*则说明是p是指针变量,而前面的int则说明是p指向的对象是int类型的。

那么我们可以顺便在学习一个操作符*p;*被称为是解引用操作符,他的意思是通过这个p中存放的地址,找到这个p所指向的对象,*p就是p所指向的对象

大家知道最近go语言比较潮流嘛

   指针变量的大小

//指针变量的大小取决于地址的大小

//32位平台下地址是32个bit位(即4个字节)

//64位平台下地址是64个bit位(即8个字节

第十节 结构体

结构体是C语言中特别重要的知识点,结构体使得C语言有能力描述复杂类型。

比如描述学生,学生包含: 名字+年龄+性别+学号 这几项信息。

这里只能使用结构体来描述了。

struct   结构体,把一些单一类型,组合到一起

#include<stdio.h>

struct stu
{
	char name[20];
	int age;
	char sex[10];
	char tele[20];
};

void print(struct stu *ps)
{
	printf("%s %d %s %s\n",(*ps).name ,(*ps).age,(*ps).sex,(*ps).tele);
	
	printf("%s %d %s %s\n", ps->name, ps->age, ps-> sex, ps->tele);
	//->
	//左边是结构体指针变量->成员名
}


int main()
{	
	struct stu s = {"zhangsan",10,"nan","19809878907" };
	printf("%s %d %s %s\n", s.name, s.age, s.sex, s.tele);
	//结构体对象.成员名,注意一点顺序是可以换掉
	//但是必须与前面的格式符相匹配。
	print(&s);
	return 0;
}

第十一节  选择语句,循环语句

C语言中如何实现循环呢?

while语句-讲解

for语句(后期讲)

if 条件语句

到这里初识C语言已经学完了,历时四天半,毕竟的曾经学过,再来一变简单的知识点还是偶尔记得,有些就直接带过了,但是我们的征程才刚刚开始,我们即将开始我们的新篇章,初阶C语言,

看了眼日期,五月一日,祝看这篇文章的朋友们五一快乐,今天看了场电影,从小就喜欢看电影,看电影对我而言就是经历了一个别样的人生,看过各种各样的电影,什么都看,豆瓣好评的,还是什么烂片,它们有些歌颂爱情,有些探讨人性,有些拥抱自由,无论是悲剧还是戏剧,无论成绩 是大还是小,是好人还是坏人,他们都有一个共同的品质——坚持!绝不放弃,是成功的必要之路!

第三章 C语言初阶

第一节 分支和循环语句

分支语句 if switch

循环语句 while for do while

goto语句

1.什么是语句

C语句可分为以下五类: 1. 表达式语句 2. 函数调用语句 3. 控制语句 4. 复合语句 5. 空语句

我们今天所学是属于控制语句

控制语句用于控制程序的执行流程,以实现程序的各种结构方式,它们由特定的语句定义符组成,C语 言有九种控制语句。 可分成以下三类:

1. 条件判断语句也叫分支语句:if语句、switch语句;

2. 循环执行语句:do while语句、while语句、for语句;

3. 转向语句:break语句、goto语句、continue语句、ret3urn语句。

2.结构

我们生活中都可以划分为   1,顺序结构    2,选择结构  if   swich     3,循环结构 for while

do  while   ,这三种,或者三种结构任意结合来描述我们生活中的各种问题

所以C语言是一种结构化的程序设计语言

3.分支语句

3.1.1if语句

语法结构:

if(表达式)    //结果为真,则欧克,但是记住非0为真

语句;

#include<stdio.h>
int main()
{
	if (8 == 8)
	{
		printf("发发发");
	}

	return 0;
}

if(表达式)    

语句1;

else    

语句2;

#include<stdio.h>
int main()
{
	int a = 11;
	if (a >10 )
	{
		printf("发发发");
	}
	else
	{
		printf("no");
	}
	return 0;
}

这里说明一点,默认是if语句后面只能跟一条语句,要多条则必须要用大括号括起来,所以习惯使用大括号是个好习惯,

//多分支    

if(表达式1)    

语句1;

else if(表达式2)    

语句2;

else    

语句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");
    }


	return 0;
}

哈哈哈哈哈哈哈这里犯了错误,快笑死了,刚才写代码的时候,先想着自己写,结果对条件的语句写成了这样18<age<=30这种,因为在python中这样是可以的,应该是,然后测试了几个数一直在报错,我的天,究竟是什么问题我纠结了许久,然后点开课,鹏哥首先举例子就是则个,反例,因为这样,你输入一个十的话,18<10,这显然是一个假的,所以为0.上面讲过非0为真,所以0就是假,0<30为真,就打印了,应该用逻辑与!!!

小结:如果条件成立,要执行多条语句,

if在这里就欧克了,注意if这种肯定只有一个答案

 如果表达式的结果为真,则语句执行。 在C语言中如何表示真假? 0表示假,非0表示真。

如果条件成立,要执行多条语句,用3一对 { } 就是一个代码块

3.1.2

悬空else

这里我们要注意一点的就是else不是很python一样有很强的缩进,他是跟他最近的if所想匹配的,不要被误导了,下面这个代码是完全不输出

//适当的使用{}可以使代码的逻辑更加清楚。 //代码风格很重要,你要是学过python你就懂了

下面这个代码还有个问题,就是if里面,你应该把常量放在左边,因为0 = a肯定报错,但是你,a = 0,这个就是赋值,可能在后面出现bug 

#include <stdio.h>
int main()
{
    int a = 0;
    int b = 2;
    if (a == 1)
        if (b == 2)
            printf("hehe\n");
        else
            printf("haha\n");
    return 0;
}
3.2 switch语句

switch语句也是一种分支语句。 常常用于多分支的情况。

switch(整型表达式) {    语句项; }

在switch语句中,我们没办法直接实现分支,搭配break使用才能实现真正的分支。

#include<stdio.h>   
int main()
{   
  int day = 0;
    scanf("%d", &day);
    switch (day)
    {
        case 1:
            printf("星期一\n");
            break;
        case 2:
            printf("星期二\n");
            break;
        case 3:
            printf("星期三\n");
            break;
        case 4:
            printf("星期四\n");
            break;
        case 5:
            printf("星期五\n");
            break;
        case 6:
            printf("星期六\n");
            break;
        case 7:
            printf("星期天\n");
            break;
        default:
            printf("请输入1-7之间的数");
            break;
    }

    return 0;
}

break语句 的实际效果是把语句列表划分为不同的分支部分。如果去掉所有break,它将从你输入是数开始出发,比方3 ,则从三口进入,直到运行完,如果在4口加了break,则为3,4,都加了break,则只显示他自己。

在最后一个 case 语句的后面加上一条 break语句。 (之所以这么写是可以避免出现在以前的最后一个 case 语句后面忘了添加 break语句)。

default

如果表达的值与所有的case标签的值都不匹配怎么办?

其实也没什么,结构就是所有的语句都被跳过而已。 程序并不会终止,也不会报错,因为这种情况在C中并不认为是个错误。

但是,如果你并不想忽略不匹配所有标签的表达式的值时该怎么办呢?

你可以在语句列表中增加一条default子句,

把下面的标签 default: 写在任何一个 case 标签可以出现的位置。

当 switch 表达式的值并不匹配所有 case 标签的值时,这个 default 子句后面的语句就会执行。

所以,每个switch语句中只能出现一条default子句。

但是它可以出现在语句列表的任何位置,而且语句流会像执行一个case标签一样执行default子句。

编程好习惯 在每个 switch 语句中都放一条default子句是个好习惯,甚至可以在后边再加一个 break 。

4,循环语句

4.1 while循环

//while 语法结构:

while(表达式)

循环语句;

#include<stdio.h>
int main()
{
	int i = 0;
	while (i<=10)
	{
		
		printf("%d\n", i);
		i++;
	}
	return 0;
}

while语句中的break和continue

break在while循环中的作用: 其实在循环中只要遇到break,就停止后期的所有的循环,直接终止循环。 所以:while中的break是用于永久终止循环的。

#include<stdio.h>
int main()
{
	int i = 1;
	while (i<=10)
	{
		i = i+1;
		if (i == 5) 
		{
			continue;
		}
		printf("%d ", i);
	
		
		
	}
	return 0;
}

输出结果为23467891011

continue在while循环中的作用就是:

continue是用于终止本次循环的,也就是本次循环中continue后边的代码不会再执行, 而是直接跳转到while语句的判断部分。进行下一次循环的入口判断。

小结一下啊:while循环中的break是直接打断,以后都不能循环了,是永久的终止循环,

continue 则是跳过下面循环语句,至今进入到判断语句,进行下一次循环的判断

讲个有意思的

#include<stdio.h>
int main()
{
	int ch = 0;
	while ((ch = getchar()) != EOF)
		putchar(ch);
	return 0;
}

这个代码用来清理缓冲区,通过while循环和getchar语句来实现这个功能;

数组名本来就是地址,所以不需要再取地址,在scanf

scanf是格式输入符,是按用户指定的格式从键盘上把数据输入到指定的变量上去

getchar跟他类似

这里有个问题是缓冲区,输入键盘再进到输入缓冲区 

#include<stdio.h>
int main()
{
	char password[20] = { 0 };
	printf("请输入您的密码:",password);
	scanf("%s", password);

	int ch = 0;
	while ((ch = getchar() != '\n'))
	{

	}
	//上面int是定义一个变量来存储getchar while里面啥都没有,意思是一直读取输入的密码,有空格都无所谓,知道读到\n,才终止循环
	printf("请确认您的密码(Y/N)");

	char ret = getchar();
	if ('Y' == ret)
	{
		printf("yes");
	}
	else
	{
		printf("no");
	}

	return 0;
}

这个代码大家一个都能看懂,在来一个

#include <stdio.h>
int main()
{
    char ch = '\0';
    while ((ch = getchar()) != EOF)
    {
        if (ch < '0' || ch > '9')
            continue;
        putchar(ch);
    }
    return 0;
}

跟第一个大差不差,不过你输入后,它只读取0-9之间的数。只打印数字字符,其中有continue的应用。

练习

#include<stdio.h>
int main()
{
	int arr[4] = { 0 };
	int i = 0;
	while (i<4)
	{
		scanf("%d", &arr[i]);
		i++;
	}

	int max = arr[0];

	i = 1;
	while (i<4)
	{
		if (arr[i] > max)
		{
			max = arr[i];
		}
		i++;
	}
	printf("%d\n", max);
	return 0;
}
//这个函数用来求四个数的最大值

练习2

#include<stdio.h>
int main()
{
	int year = 0;
	int month = 0;
	int day = 0;
	scanf("%4d%2d%2d", &year, &month, &day);
	printf("year = %d\n", year);
	printf("month = %02d\n", month);//02d则是在说明,我要打印两位,并且补齐两位
	printf("day = %d\n", day);//这样打印也是两位但是不会补0
	return 0;
}5
//上述函数实现了一个功能就是输入不间断的年月日,输出分开的年月日
4.2 for循环

语法

for(表达式1; 表达式2; 表达式3)

循环语句;

表达式1 表达式1为初始化部分,用于初始化循环变量的。

表达式2 表达式2为条件判断部分,用于判断循环时候终止。

表达式3 表达式3为调整部分,用于循环条件的调整。

现在我们对比一下for循环和while循环

#include<stdio.h>
int main()
{
	int i = 0;
	//实现相同的功能,使用while
	i = 1;//初始化部分
	while (i <= 10)//判断部分
	{
		printf("hehe\n");
		i = i + 1;//调整部分
	}
	//实现相同的功能,使用while
	for (i = 1; i <= 10; i++)
	{
		printf("haha\n");
	}
	return 0;

可以发现在while循环中依然存在循环的三个必须条件,但是由于风格的问题使得三个部分很可能偏离较 远,这样 查找修改就不够集中和方便。所以,for循环的风格更胜一筹;for循环使用的频率也最高。

我们发现在for循环中也可以出现break和continue,他们的意义和在while循环中是一样的。 但是还是有些差异:

大致相同,不同点在continue后面他的执行语句随着continue不能执行,直接进入到了判断语句,而for则能够再次执行,因为在前面。

 for语句的循环控制变量

建议:

1. 不可在for 循环体内修改循环变量,防止 for 循环失去控制。

2. 建议for语句的循环控制变量的取值采用“前闭后开区间”写法。

int i = 0;

//前闭后开的写法 for(i=0; i<10; i++) {}

//两边都是闭区间 for(i=0; i<=9; i++) {}

看个人喜好吧,反正我习惯是前闭后开的写法;

一些for循环的变种

#include<stdio.h>
int main()
{
    int i = 0;
    int j = 0;
    int count = 0;
    //这里打印多少个hehe?
    for (i = 0; i < 10; i++)
    {
        for (j = 0; j < 10; j++)
        {
            printf("hehe%d\n",count++);
        }
    }
	return 0;
}//100个
#include<stdio.h>
int main()
{
    int i = 0;
    int j = 0;
    int count = 0;
    //这里打印多少个hehe?
    for (; i < 10; i++)
    {
        for (; j < 10; j++)
        {
            printf("hehe%d\n",count++);
        }
    }
	return 0;
}

这个为十个,这两者的区别在于省略了第一个表达式,第一个没有省略初始化,在i= 0时,j从0—9,继而i=1也是这样一共为100,因为每次循环的初始值都给了0,而后者,则在一次过后j的值变成了11,循环结束,所以只有是个

//代码4-使用多余一个变量控制循环

#include<stdio.h>
int main()
{
    int x, y;
    for (x = 0, y = 0; x < 2 && y < 5; ++x, y++)
    {
        printf("hehe\n");
    }
    return 0;

}
4.3 do while 循环

do语句的语法:

do

循环语句;//上来就直接干!

while(表达式);

它这个至少要执行一次,条件比较苛刻;

do里面多条语句带大括号

break 和continue,用法跟其他一样就不再多说了;

小例子,

编写代码实现,模拟用户登录情景,并且只能登录三次。(只允许输入三次密码,如果密码正确则
//提示登录成,如果三次均输入错误,则退出程序。

#include<stdio.h>
int main()
{
	int i = 0;
	char password[20] = { 0 };
	char key[] = "zxc";
	
	while (i < 3)
	{
		printf("请输入您的密码:%s\n", password);
		scanf("%s", password);

		if (!strcmp(key, password))
		{
			printf("密码正确");
			return 0;
		}
		else
		{
			printf("密码错误\n");
			i++;
		}

	}

	return 0;
}

这里有一点十分重要,比较两个字符串是否相当不能使用==,而应该使用一个库函数stlcmp,

重要

ret = strcmp(str1, str2); ret = 0,两个字符串相等

这里在说一个小方法,当你不知道函数用法时,百度是一个很好的用法,哈哈哈,钻牛角尖了,大佬一指导茅塞顿开。

#include<stdio.h>
int main()
{
	int i = 0;
	char password[20] = { 0 };

	for (; i < 3; i++)
	{
		printf("请输入你的密码\n");
		scanf("%s", password);
		if (strcmp(password, "zxcvbnm") == 0)
		{
			printf("登录成功\n");
			break;
		}
		else
		{
			printf("登录失败,密码错误\n");
		}
	}
	if (i == 3)
	{
		printf("三次输入均错误,退出程序");
	}

	return 0;

}

另一种方法

#include<stdio.h>
int main()
{
	char psw[10] = "";
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; ++i)
	{
		printf("please input:");
		scanf("%s", psw);
		if (strcmp(psw, "password") == 0)
			break;
	}
	if (i == 3)
		printf("exit\n");
	else
		printf("log in\n");
}

补充一个常用库函数strlen 这个长用来计算字符串是长度,跟sizeof不同一个是库函数,一个是关键字,前者不计算\n,后者算,对一个数组还是其他,前者减一,后者减二,有利

#include<stdio.h>
#include <string.h>
#include<Windows.h>
#include<stdlib.h>

int main()
{
	char arr1[] = "welcome to bit !!!";
	char arr2[] = "##################";
	int left = 0;
	int right = strlen (arr2)-1;//注意这个strlen的用法

	while (left<=right)
	{
		arr2[left] = arr1[left];
		arr2[right] = arr1[right];
		printf("%s\n", arr2);
		Sleep(1000);//效果延时1000毫秒
		left++;
		right--;
	}
	return 0;
}

例子三,有序数组二分法

#include<stdio.h>
int main()
{
		int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
		int left = 0;
		int right = sizeof(arr) / sizeof(arr[0]) - 1;
		int key = 8;
		int mid = 0;
		while (left <= right)
		{
			mid = (left + right) / 2;
			if (arr[mid] > key)
			{
				right = mid - 1;
			}
			else if (arr[mid] < key)
			{
				left = mid + 1;
			}
			else
				break;
		}
		if (left <= right)
			printf("找到了,下标是%d\n", mid);
		else
			printf("找不到\n");
	
	return 0;
}

例子四:

这节是个时长两个半小时的大电影,我每次笔记都是边看边做,但是这个属实是给我看累了

开始启航吧,我们的猜数字小游戏!

我们要如何实现这个了,

首先我们得实现一个问题就是产生一个一定范围的随机数,

然后当我们猜一个数字时,它有相应的反馈,

一种,你运气特别好,猜中了,

另一种是猜错了,猜错了应该也有一个反馈,比你猜的那个数大还是小,

再丰富补充一下,来个菜单

实现这几个功能,就能出关了

#include<stdio.h>
#include<time.h>

void menu()
{
	printf("**********************************\n");
	printf("*********** 1.play     **********\n");
	printf("*********** 0.exit     **********\n");
	printf("**********************************\n");
}


	//rand函数,可以生产一个随机数
	// 调用rand函数前,我们可以利用srand来对他进行一个设置
	//这里我们学了时间戳
void game()
{
	int ret = rand()%100+1;
	int guess = 0;
	while (1)
	{
		printf("请输入猜的数字(0-100之间)>:");
		scanf("%d", &guess);
		if (guess > ret)
		{
			printf("猜大了\n");
		}
		else if (guess < ret)
		{
			printf("猜小了\n");
		}
		else
		{
			printf("恭喜你,猜对了\n");
			break;
		}
	}
}

int main()
{
	srand((unsigned)time(NULL));//int*p = null就是0,本质上就是0,空指针。
	int input = 0; 
	do
	{
		menu();
		printf("请选择:>");

		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏");
			break;
		default:
			printf("请输入正确的数字");
			break;
		}
	} while (input );
	
	return 0;
}

这里面有两个挑战,一个是srand函数进行设置,再用时间栈来对他进行真正随机,如果是里面参数为一个常数,那么它的里面随机数每次都是一样,你多玩几次就没有意思了,这里就用到一个函数,time,首先定义头文件#include<time.h>里面参数一般使用null,就是一个空指针,

4.4,goto语句

goto语句水太深,你把握不住:

它不能跨函数来使用

C语言中提供了可以随意滥用的 goto语句和标记跳转的标号。

从理论上 goto语句是没有必要的,实践中没有goto语句也可以很容易的写出代码。

但是某些场合下goto语句还是用得着的,

最常见的用法就是终止程序在某些深度嵌套的结构的处理过 程。

#include<stdio.h>
int main()
{
	again:
	printf("hehe");
	Sleep(1000);
	printf("haha");
	Sleep(1000);
	goto again;
	return 0;
}

例如:一次跳出两层或多层循环。 多层循环这种情况使用break是达不到目的的。它只能从最内层循环退出到上一层的循环。

for(...)
    for(...)
   {
        for(...)
       {
            if(disaster)
                goto error;
       }
   }
    …
error:
 if(disaster)
         // 处理错误情况

关机程序:

电脑运行一分钟后就关机,如果输入我是猪就不关机了,这个可好玩哦,一不小心就关机了

#include<stdio.h>
int main()
{
	char input[20] = { 0 };
	system("shutdown -s -t 60");

again:
	printf("您的电脑将在60s后关机,输入“我是猪”取消关机\n");
	scanf("%s", input);
	if (!strcmp(input,"我是猪"))
	{
		printf("明智的选择,小猪");
		system("shutdown -a");
	}
	else
	{
		goto again;
	}

	return 0;
}

这个我做了一点升级,与上面的猜数字结合在一起了

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
	system("shutdown -s -t 6000");
	srand((unsigned)time(NULL));
	int ret = rand() % 100 + 1;

	
	int guess = 0;

	
	
	while (1)
	{
		printf("您的电脑将在60s后关机,猜个数字吧(0-100之间),猜对就取消关机>:\n");
		scanf("%d", &guess);
	
		if (guess > ret)
		{
			printf("猜大了,继续加油哦\n");
		
		}
		else if (guess < ret)
		{
			printf("猜小了,时间不多了哦\n");
		
		}
		else
		{
			system("shutdown -a");
			printf("恭喜你,猜对了\n");
		
			break;
		}

	}
	return 0;
}

这个大家不用乱搞,很有意思是一老铁,写进服务里面,而且是自启动,然后加死循环,结果就是电脑卡死,关机重启不管用,真的狠,大家有空就随便写着玩玩吧,你都学C了,应该没对象吧!

哈哈哈哈哈哈,我也没有。。。。。

debug是调试版本   程序调试版本

release是发布版本,发布给用户使用的版本

用release可以有一个exe文件,可以在Windows系统上直接打开

这两天这几个小时的题,整自闭了,不过还好,马上就开始新的篇章,

第二节,函数

1. 函数是什么

 维基百科中对函数的定义:子程序 在计算机科学中,子程序(英语:Subroutine, procedure, function, routine, method, subprogram, callable unit),是一个大型程序中的某部分代码, 由一个或多个语句块组 成。

它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。

一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。

这些代码通常被集成为软 件库。

2. 库函数

C语言中函数的分类:

1. 库函数 2. 自定义函数

为什么会有库函数?

1. 我们知道在我们学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想 把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能:将信息按照一定的格 式打印到屏幕上(printf)。

2. 在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)。 3

. 在编程是我们也计算,总是会计算n的k次方这样的运算(pow)。

像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到, 为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员 进行软件开发。 那怎么学习库函数呢?

这里我们简单的看看:www.cplusplus.com

这里我们学到一个新知识:

虚拟机,就是装一个软件wmare workstation 虚拟一个电脑

简单的总结,C语言常用的库函数都有:

IO函数 字符串操作函数 字符操作函数 内存操作函数 时间/日期函数 数学函数 其他库函数

注: 但是库函数必须知道的一个秘密就是:使用库函数,必须包含 #include 对应的头文件。 这里对照文档来学习上面几个库函数,目的是掌握库函数的使用方法。

需要全部记住吗?No 需要学会查询工具的使用:

MSDN(Microsoft Developer Network) www.cplusplus.com

http://en.cppreference.com(英文版) http://zh.cppreference.com(中文版) 英文很重要。

最起码得看懂文献

3. 自定义函数

如果库函数能干所有的事情,那还要程序员干什么?

所有更加重要的是自定义函数。

自定义函数和库函数一样,有函数名,返回值类型和函数参数。

但是不一样的是这些都是我们自己来设计。这给程序员一个很大的发挥空间。

函数的组成:

et_type fun_name(para1, * )

{ statement;//语句项 }

ret_type 返回类型   fun_name 函数名      para1    函数参数

当,实参传给形参时,形参是实参的一个拷贝,

对形参进行修改不会影响实参,

所以如果实现两个数的交换,我们可以用出我们的!!!指针

指针大人!!!!!!!!

#include<stdio.h>
//实现成函数,但是不能完成任务
void Swap1(int x, int y)
{
	int tmp = 0;
	tmp = x;
	x = y;
	y = tmp;
}
void Swap2(int* px, int* py)
{
	int tmp = 0;
	tmp = *px;
	*px = *py;
	*py = tmp;
	
}
int main()
{
	int num1 = 1;
	int num2 = 2;
	Swap1(num1, num2);
	printf("Swap1::num1 = %d num2 = %d\n", num1, num2);
	Swap2(&num1, &num2);
	printf("Swap2::num1 = %d num2 = %d\n", num1, num2);
	return 0;
}

#include<stdio.h>
int get_max(int a, int b)
{
	return a > b ? a : b;

}

int main()
{
	int x = 1;
	int y = 2;
	int z = 0;
	z = get_max(x, y);
	printf("%d", z);


	return 0;
}
4. 函数参数

 实际参数(实参): 真实传给函数的参数,叫实参。 实参可以是:常量、变量、表达式、函数等。 无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。

形式参数(形参): 形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内 存单 元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。

这里可以看到 Swap1 函数在调用的时候, x , y 拥有自己的空间,同时拥有了和实参一模一样的内容。 所以我们可以简单的认为:形参实例化之后其实相当于实参的一份临时拷贝。

5. 函数调用

传值调用:

函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。

传址调用:

传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。

这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。

练习一下:

1,判断一个数是不是素数:


#include<stdio.h>


int main()
{
	int count = 0;
	int i = 0;
	for ( i = 1; i <= 200; i++)
	{
		int flag = 1;//是1则是素数
		int j = 0;
		for ( j = 2; j <= i-1; j++)
		{
			if (i%j==0)
			{
				flag = 0;
				break;
			}
		}
		if (flag==1)
		{
			count++;
			printf("%d  %d\n", count,i);
		}
	}


	return 0;

}

2.写一个函数判断是不是闰年

函数的功能要尽量的单一,高类聚低耦合。


#include<stdio.h>

int is_leap_year(int year)
{
	if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
	{
		return 1;

	}
	else
	{
		return 0;
	}
}

int main()
{
	int year = 0;
	scanf("%d", &year);
	
	if (is_leap_year(year) )
	{
		printf("%d是润年", year);
	}
	else
	{
		printf("%d不是润年", year);
	}
	return 0;
}

3写一个函数,实现整型有序数组的二分查找

这里有个想法就是先写函数怎么用,再去实现函数的功能

#include<stdio.h>

int binary_search(int arr[], int k, int sz)
{
	int left = 0;
	int right = sz - 1;
	int mid = left + (left - right) / 2;
	while(left<=right)
	{
		if (arr[mid] < k)
		{
			left = mid + 1;
		}
		else if (arr[mid] > k)
		{
			right = mid - 1;
		}
		else
		{
			return mid;
		}
	}
	return -1;
}

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9, 10};
	int k = 7;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int ret = binary_search(arr, k, sz);
	if (ret == -1 )
	{
		printf("找不到目标");
	}
	else
	{
		printf("%d", ret);
	}
	return 0;
}

反正我是真的真的不喜欢二分这个函数,不想写了,有些错,大佬帮忙纠错一下。

布尔类型

4,写一个函数,每次用到这个函数num加一

#include<stdio.h>
void ADD(int*p)
{
	(*p)++;
}

int main()
{
	int num = 0;
	ADD(&num);
	printf("%d\n", num);
	ADD(&num);
	printf("%d\n", num);
	return 0;
}
//如果你想要一个函数来改变它外部的变量的时候,可以把它的地址传进来,
//然后在函数内部通过指针,找到外部的变量,然后进行修改
6. 函数的嵌套调用和链式访问

函数可以嵌套调用,但是不能嵌套定义。

函数都是平等的,不能在一个里面嵌套一个函数

#include <stdio.h>
void new_line()
{
    printf("hehe\n");
}
void three_line()
{
    int i = 0;
    for (i = 0; i < 3; i++)
    {
        new_line();
    }
}
int main()
{
    three_line();
    return 0;
}

链式访问

把一个函数的返回值作为另外一个函数的参数。

#include<stdio.h>
#include<string.h>
int main()
{

	int len = strlen("abcdef");
	printf("%d\n", len);
	//链式访问,把一个函数的返回值,当作另一个函数的参数
	printf("%d\n", strlen("abcdef"));

	return 0;
}

一个很经典的例子

#include <stdio.h>
int main()
{
    printf("%d", printf("%d", printf("%d", 43)));
    //结果是啥?
    //注:printf函数的返回值是打印在屏幕上字符的个数
    return 0;
}

链式反应,函数必须要有返回值

小科普:本质上main函数是有3个参数的。int main(int argc  char*argv charr*envp[])

7. 函数的声明和定义

程序从上往下执行,使用前记得先定义,不然会报错未定义

函数声明:

1. 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。

2. 函数的声明一般出现在函数的使用之前。要满足先声明后使用。

3. 函数的声明一般要放在头文件中的。

函数定义: 函数的定义是指函数的具体实现,交待函数的功能实现。

初学代码,总觉得写在一个.c文件里面写,

但是在公司里,为了更好实现这个,我们都要进行一个模块化,封装化

比方设计一个计算机,我们分块,进行加减乘除,写四个.c .h

我们现在讨论一个问题,为什么要把函数的声明和定义分开了

这里有三个好处:

头文件的包含就是把头文件那些拷贝过来

为了以后当大佬,买东西分开留一手,右键属性建成静态库add.lip

如何使用呢,

导入静态库的方法

#pragma comment(lib "add.lip")就可以使用了

8. 函数递归

程序调用自身的编程技巧称为递归( recursion)。

递归做为一种算法在程序设计语言中广泛应用。

一个过程或函数在其定义或说明中有直接或间接 调用自身的 一种方法,

它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,

递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

递归的主要思考方式在于:把大事化小

递归的两个必要条件存在限制条件,

当满足这个限制条件的时候,递归便不再继续。

每次递归调用之后越来越接近这个限制条件

#include <stdio.h>
void print(int n)
{
	if (n > 9)
	{
		print(n / 10);
	}
	printf("%d ", n % 10);
}
int main()
{
	int num = 1234;
	print(num);
	return 0;
}

很牛比的一个函数,看懂这个函数,你就真正的懂了递归,递归递归,先递后归,

不需要return,是因为函数掉完会自动返回

这里有一个常见报错,stack overflow,栈溢出错误,(内存里面分三个还记得吗:栈区,堆区,静态取),每一次函数调用,都会在栈区申请空间,栈区里面还有局部变量,形参等

字符串传参的时候跟数组是类似的,只需要第一个字符的地址就可以使用

#include<stdio.h>

int my_strlen(char* str)
{
	int count = 0;//jishuqi
	while  (*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}

int main()
{
	char arr[] = "abc";//[a,b,c,\0]
	int len = my_strlen("arr");
	printf("%d\n", len);
	return 0;
}
#include<stdio.h>

int my_strlen(char* str)
{
	if (*str != '\0')
	{
		return 1 + my_strlen( str+1);
	}
	else
	{
		return 0;
	}

}

int main()
{
	char arr[] = "abc";//[a,b,c,\0]
	int len = my_strlen("arr");
	printf("%d\n", len);
	return 0;
}

上面是采用递归的方式来写的

递归与迭代

计算一个斐波那契数列(前两个数列相加等于第三个数列)

#include<stdio.h>

int fib(int n)
{
	if (n <=2)
	{
		return 1;
	}
	else
	{
		return fib(n - 1) + fib(n - 2);
	}


}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = fib(n);
	printf("%d\n", ret);

	return 0;
}

可行,但数字很大就很慢,低效率,

那如何解决i的问题:

1. 将递归改写成非递归。

2. 使用static对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 static 对象替代 nonstatic 局部对象(即栈对象),这不 仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保 存递归调用的中间状态,并且可为 各个调用层所访问。

我们可以用非递的函数来解决这个问题

#include<stdio.h>

int fib(int n)
{
	int a = 1;
	int b = 1;
	int c = 1;
	while (n >= 3)
	{
		c = a + b;
		a = b;
		b = c;
		n--;

	}
	return c;

}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = fib(n);
	printf("%d\n", ret);

	return 0;
}

修改后

1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。 2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。 3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开 销。

第三节 数组

1. 一维数组的创建和初始化

首先我们思考一个问题,什么是数组,

数组:是一组具体相同类型元素的集合

type_t   arr_name   [const_n];

//type_t 是指数组的元素类型 //const_n 是一个常量表达式,用来指定数组的大小

int arr[10];

int就是函数的类型  arr则是数组的名字,[10]则是里面有十个元素

char ch [5]类比也可以写出来

 //代码2

int count = 10; i

nt arr2[count];

/数组时候可以正常创建? /

注:数组创建,在C99标准之前, [] 中要给一个常量才可以,不能使用变量。在C99标准支持了变长数 组的概念。

2. 一维数组的使用

int arr[10] = {1,1,2,3,},这种叫不完全初始化,其他的默认为0了

char arr1[10] = "abc";

char arr2[10] = {'a','b','c'};

这两个是不太相同的,前者是a,b,c,\0,然后补0.后者则是没有\0,直接补0

数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确 定。

char arr5[] = {'a','b','c'};

int arr6[] = {1,2,3}

这种电脑给你自动给你计算,注意一点就是字符串比那个多一个,因为有\0的存在。

3. 一维数组在内存中的存储

对于数组的使用我们之前介绍了一个操作符: [] ,下标引用操作符。它其实就数组访问的操作符。

总结: 1. 数组是使用下标来访问的,下标是从0开始。 2. 数组的大小可以通过计算得到。


#include<stdio.h>
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for ( i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}


	return 0;
}

接下来我们探讨数组在内存中的存储。

上述代码打印更改为printf("&arr[%d] = %p \n", i,&arr[i]);

可得到结果

&arr[0] = 0000005F76AFF5D8
&arr[1] = 0000005F76AFF5DC
&arr[2] = 0000005F76AFF5E0
&arr[3] = 0000005F76AFF5E4
&arr[4] = 0000005F76AFF5E8
&arr[5] = 0000005F76AFF5EC
&arr[6] = 0000005F76AFF5F0
&arr[7] = 0000005F76AFF5F4
&arr[8] = 0000005F76AFF5F8
&arr[9] = 0000005F76AFF5FC

每个电脑都是不一样的,观察后四位,可发现,十六进制,一下子联想到我们int一个数占四个字节

由此我们可以得到一个结论:数组在内存中是连续增长的

4. 二维数组的创建和初始化

//数组创建

int arr[3][4];

char arr[3][5];

double arr[2][4];

初始化

int arr[3][4] = {1,2,3,4};

int arr[3][4] = {{1,2},{4,5}};

int arr[][4] = {{2,3},{4,5}};//二维数组如果有初始化,行可以省略,列不能省略

5. 二维数组的使用

#include<stdio.h>
int main()
{
	int arr[][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 };
	int i = 0;
	for (size_t i = 0; i < 3; i++)
	{
		int j = 0;
		for (size_t j = 0; j < 4; j++)
		{
			printf("%d", arr[i][j]);//这个代码是实现打印出所有的数组
		}
		printf("\n");
	}
	printf("%d", arr[2][0]);
	return 0;
}

二维数组的使用也是通过下标的方式。跟一维差不多,都是从0开始

6. 二维数组在内存中的存储

像一维数组一样,这里我们尝试打印二维数组的每个元素。

通过结果我们可以分析到,其实二维数组在内存中也是连续存储的。

arr0 arr1 arr2这样子连续存储

7. 数组越界

数组的下标是有范围限制的。

数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。

所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。

C语言本身是不做数组下标的越界检查,编译器也不一定报错,

但是编译器不报错,并不意味着程序就 是正确的,

所以程序员写代码时,最好自己做越界的检查。

8. 数组作为函数参数

往往我们在写代码的时候,会将数组作为参数传个函数,比如:我要实现一个冒泡排序(这里要讲算法 思想)函数 将一个整形数组排序。

冒泡排序的核心思想:两个相邻的元素相比较,不是相邻,则开始

一套冒泡培训让一个元素到他的位置上   n个元素,则需要n-1趟


#include<stdio.h>
//数组传参数形参有两个写法:
//1,数组的形式 arr[]
//2,指针 
void bubble_short(int arr[])
{
	//趟数
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (  i = 0; i < sz; i++)
	{
		//一趟冒泡培训
		int j = 0;
		for (j = 0; j <sz-1 ; j++)
		{
			if (arr[j] < arr[j+1])
			{
				int tem  =  arr[j];
				arr[j] =  arr[j + 1];
				arr[j + 1] =  tem;
			}
		}
		
	}


}

int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	// 0 1 2 3 4 5 6 7 8 9 
	//冒泡排序的算法,对数组进行一个排序
	bubble_short(arr);//传参给数组名,不要给arr[]

	return 0;
}

方法1,出问题,那我们找一下问题,调试之后可以看到 bubble_sort 函数内部的 sz ,是1。 难道数组作为函数参数的时候,不是把整个数组的传递过去?

我们可以看到,数组名它本质上就是数组首元素的地址,应该用指针,所以传过去,看似是数组,本质上是指针

9. 数组的应用实例1:三子棋

10. 数组的应用实例2:扫雷游戏

a

不过是些许风霜罢了

第四节  操作符详解

1.算术操作符

1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。

2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。

3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数。

#include<stdio.h>
int main()
{
	
	printf("%d\n",  1 / 2);

	printf("%f\n",1 / 2);

	return 0;
}

2.移位操作符

 注:移位操作符的操作数只能是整数。

<<左移操作符 移位规则: 左边抛弃、右边补0

这里我们需要知道二进制的二进制的一些知识,涉及到正数,负数的源码,反码,补码,的一些知识,我们首先可以知道,正数的源码,反码,补码,都一样,而负数的反码则是符号位不变,其他取反,补码则是,反码加一。

这些都是计算机基本知识,我们还需要知道就是,整数在计算机中是以补码的形式存储的。


#include<stdio.h>
int main()
{
	int a = -7;
	int b = a << 1;
	printf("%d\n", a);
	printf("%d\n",b);
	return 0;
}

右移操作符

移位规则: 首先右移运算分两种:

1. 逻辑移位 左边用0填充,右边丢弃

2. 算术移位 左边用原该值的符号位填充,右边丢弃

  

#include<stdio.h>
int main()
{
	int a = -7;
	int b = a >> 1;
	printf("%d\n", a);
	printf("%d\n",b);
	return 0;
}

结果是-4,所以用的是算术移位,这个不一定,取决与编译器

正数无论是算术还是逻辑都一样,打印出来都一样是个3,

3.位操作符

& //按位与

| //按位或

^ //按位异或

注:他们的操作数必须是整数。

%d意味着打印一个有

4.赋值操作符

5.单目操作符

6.关系操作符

7.逻辑操作符

8.条件操作符

9.逗号表达式

10.下标引用、函数调用和结构成员

第五节,指针

1. 指针是什么

2. 指针和指针类型

3. 野指针

4. 指针运算

5. 指针和数组

6. 二级指针

7. 指针数组

1. 指针是什么

指针理解的2个要点:

1. 指针是内存中一个最小单元的编号,也就是地址

2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量

总结:指针就是地址,口语中说的指针通常指的是指针变量。

指针变量 我们可以通过&(取地址操作符)取出变量的内存其实地址,

把地址可以存放到一个变量中,这个 变量就是指针变量

#include <stdio.h>
int main()
{
	int a = 10;
	int *p = &a;
	printf("%p\n", &a);
	printf("%p\n", p);
	//两者地址一样

	return 0;
}

指针变量:存放地址的变量(存放在指针中的值都被当成地址处理)

变量a和指针变量pa都有各自的地址,只是把变量a的地址存放在指针变量pa里。

这里我们就明白:

在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以 一个指针变量的大小就应该是4个字节。

那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地 址。

总结: 指针是用来存放地址的,地址是唯一标示一块地址空间的。 指针的大小在32位平台是4个字节,在64位平台是8个字节。

#include <stdio.h>
int main()
{
	char* pc= NULL;
	short* ps = NULL;
	int* pi = NULL;
	double* pd = NULL;

	printf("%zu\n", sizeof(pc));
	printf("%zu\n", sizeof(ps));
	printf("%zu\n", sizeof(pi));
	printf("%zu\n", sizeof(pd));

	return 0;
}

2. 指针和指针类型

要将&num(num的地址)保存到p中,我们知道p就是一个指针变量,那它的类型是怎样的呢? 我们给指针变量相应的类型。

这里可以看到,指针的定义方式是: type + * 。

其实: char* 类型的指针是为了存放 char 类型变量的地址

 short* 类型的指针是为了存放 short 类型变量的地址。

int* 类型的指针是为了存放 int 类型变量的地址。

float*类型的指针是为了存放float类型变量的地址

指针类型的意义是什么?

1.指针类型决定了指针在解引用的时候访问几个字节

例子:int*的指针,解引用的时候,访问四个字节

2.指针类型决定了指针在加减1时的,跳过字节大小,决定了步长

3.野指针

概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

1. 指针未初始化

2. 指针越界访问

3. 指针指向的空间释放

如何规避野指针

1. 指针初始化

2. 小心指针越界

3. 指针指向空间释放即使置NULL

4. 避免返回局部变量的地址

5. 指针使用之前检查有效性

4. 指针运算

指针+- 整数 指针-指针 指针的关系运算

*vp++   相当于vp*,vp++

(vp*)++ 相比于(vp*)++

指针-指针

得到指针与指针之间元素的个数

注意:不是所有的指针都能相减,指向同一块空间的两个指针才能相减

指针的关系运算

允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。

5. 指针和数组

结论:数组名表示的是数组首元素的地址。

6. 二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里? 这就是 二级指针 。

对于二级指针的运算有: *ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa

int **ppa = &pa

7. 指针数组

指针数组是指针还是数组? 答案:是数组。

是存放指针的数组。 数组我们已经知道整形数组,字符数组。

那指针数组是怎样的?

int* arr3[5];

arr3是一个数组,有五个元素,每个元素是一个整形指针。

最近三天都没咋更新,因为去进阶了下32,个人感觉32还不是很难,毕竟只要是能写出来,大家只要下功夫都可以学会嘛,只是基础不同,大家的认知,精神世界也不同罢了,不同的生活环境,构造出我们不同的个体,个体独立性就是这样。我看到一个说法就是把认知的世界划分为11个层面,就是物质世界,精神世界等那样划分,区别在与认知,看待事物的本质等,果然到最后还是哲学

  • 10
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值