C语言基础笔记(嵌入式系列学习1)

嵌入式学习系列将从C语言基础学习开始,本篇主要讲述了C语言基础主要知识总结,用于嵌入式基础巩固,开篇普及了一点虚拟机基本操作指令,从C语言基础、数组、函数、结构体和内存管理等总结简要,编译环境可选择Ubuntu、VScode、VS等。

整篇是本人通过学习进行笔记记载,如有不足,还请不要吝啬指出。另外我们提供嵌入式项目毕设资料与服务,远程部署、代码讲解、全方位服务。我们有专门的毕设项目辅导群(QQ:596557438)!!!

项目源码下载地址:箫声商城

一、虚拟机基本操作


1.基础

VMware:虚拟机

Ubuntu:Linux操作系统中的一种

windows: GUI设计非常完善 用户多 系统不够稳定 不够安全 收费 不开源

Linux: GUI设计完善 免费 开源 需要一定的学习 一切皆文件

1)Linux相关指令:

①pwd :获取当前目录的绝对路径(从根目录/开始)

②ls : 查看文件

        ls 默认当前目录

        ls <指定路径> 查看指定路径下的文件

        ls -l 查看文件的详细信息

        ls -a 查看所有文件

③cd进入某个目录

        cd默认进入当前用户的工作目录(~)

        cd-回到上一次所在的目录

        cd ..回到上一级所在目录

        cd<指定路径>进入到指定目录

④mkdir<文件名>创建一个文件夹

⑤rmdir<文件名> 删除一个空文件夹

⑥rm删除文件/unlink

        unlink命令用于删除文件和链接,其中链接包括软链接和硬链接。它是最简单的命令之一,除了--help以及--version以外,没有任何其他选项

        使用unlink删除文件,可以这样:

                unlinkfilename

        删除链接,可以这样使用:

                unlinklink_name

        删除成功后,不会有任何输出

                unlink不能同时删除多个文件和链接

rm命令可以一次删除多个文件。但是unlink却不能,它一次只能删除一个文件或者链接,而且,也不能使用全局模式(globbingpatterns)。

使用unlink不能删除目录

        GNU实现下的unlink命令是无法删除目录的,如果给定的文件名称是一个目录,就会报错

⑦farsight@ubuntu:~$

        用户名 @主机名:路径(~) 命令提示符($)

⑧vi/vim/touch/gedit 文件名

        vi/vim文件名: 创建文件/打开文件

命令行模式:

复制(nyy,从光标所在行开始复制n行)、粘贴(p,在光标下一行开始粘贴)、剪切(ndd)、撤销(u)...

按     i:在光标所在位置插入

        a:在光标之后插入

        o:在下一行插入 ,进入插入模式

        shift+: 进入底行模式

插入模式:

        编辑文本内容

        按Esc进入命令行模式

底行模式:

        操作文件保存(w)、退出(q) x(保存并退出)

        按Esc进入命令行模式

        ctrl+d:表示结束输入

2.在linux中进行编译的注意事项

1)在使用某些非标准库时除了需要添加头文件外还应该链接对应库如:

#include <math.h>

gcc xxx.c -o xxx -lm -l:表示链接某个库 m:math库的名称

二、C语言基础知识


1.进制转换


进制转换:  2^0=1 2^1=2 2^2=4 2^3=8 2^4=16 2^5=32 2^6=64 2^7=128 2^8=256     
二进制转10进制: 10101 = 12^4+12^2+1*2^0    
二进制转8进制:  100010010 = 0422 (前面0表示8进制)    
二进制转16进制: 11010100101b = 0x6a5  (前面0x表示16进制)    
十进制转二进制:  凑数法: 将十进制数凑成不同2的整数次幂相加  
    550 = 512 + 32 + 4 + 2 = 2^9 + 2^5 + 2^2 + 2^1 = 1000100110
表示字符时:' '  、字符串:"  "  
 '0'   0   "0"

 

 

2.变量和常量

常量: 在程序执行的整个过程中不允许(不能)改变的量

变量: 在程序执行的整个过程中允许(能)改变的量

C语言大小写敏感(区分大小写),关键字必须为小写


#include <stdio.h>
int main()
{
    intsum=1+2+3+4+5+6+7+8+9+10;
    printf("%d\n", sum);
}
//非0为真

3.整形数据在内存中的存储方式

1)整型数据在内存中的存储方式:

整型数据在内存中都是按照补码存储

符号位: 数据的最高位为符号位,0为正1为负

原码: 数据本身的二进制

反码: 符号位不变,其它位按位取反

补码: 正数的补码就是其原码,负数的补码是它的反码加1

0没有正负之分


 00000000  +0
    128:10000000  -0  //-0表示最小(它的值域范围内)的负数-1
         1000000000000000-(2^15-1)-1->-2^15
                            -2^31
​
255: 补码: 11111111
     反码: 11111110
     原码:  10000001
    
151:128+16+4+2+1
    补码:10010111
    反码:10010110
    原码:11101001
    -105
char a=127+1
char a=1;
00000001-->11111111-->100000000-->00000000
标识常量主要注意它是原样替换不会改变运算优先级
2)基本数据类型的取值范围

数据类型

字节数

取值范围

char

1

-128~127

unsigned char2

1

0~255

short

2

-32768~32767

unsigned short

2

0~65535

int

4

-2147483648~2147483647

unsigned int

4

0~4294967295

long

4

-2147483648~2147483647

unsigned long

4

0~4294967295

long long

8

-9223372036854775808~9223372036854775807

unsigned long long

8

0~18446744073709551615

float

4

1.1754910^-38~3.4028210^38

double

8

2.2250710^-308~1.7976910^308

long double

12

2.2250710^-308~1.7976910^308

4.变量的一般形式

变量的一般形式: <存储类型> <数据类型> <变量名>;

初始化: 在变量定义时就赋初始值

赋值: 在定义之后再去给变量一个值


int a;
a=10; //赋值
int b=20; //初始化
int a=10, b=20; //初始化
a=b;  //赋值
int a=10, b=a;   //初始化
int b=a, a=10;  //错误, a未定义

强制数据类型的转换是指采用某种方式将某种数据类型强制转换成指定的数据类型。这种转换存在两种方式:一种为显式的数据类型转换,另一种为隐式的数据类型转换

显式的数据类型转换实现的一般形式为: (数据类型名称)< 表达式 >


在C语言中char和short在参与算术运算时都会自动变为int
char a='A'+2
short b=a+2;
​
int a= (int)(4.9+1.1);
float b=3.14;
int a= (int)b+3;
b=3.14

5.运算符

1)运算符优先级

 

2)算术运算

/ % ++ -- + - *

/: 如果左右两边都是整数,它代表的是整除,如果左右两边只要有一个是浮点数, 则结果一定是浮点数

%:它只能用于整数运算,求的是余数

++:前++表示先自己加1,再参与运算 后++表示先参与运算,再加1

--:同上,只不过加1变成减1


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

3)逻辑运算符

&& || !

&&:左右两个操作数都为真,结果为真,如果左右操作数有一个或都为假,结果一定为假短路法则


int a=0, b=1, c;
c=a && ++b;
printf("%d %d %d\n", a, b, c);  //0 1 0

test:
int a=0, b=1, c;
c=a++&&++b;
printf("%d %d %d\n", a, b, c);

int a=0, b=1, c;
c=++a&&++b;
printf("%d %d %d\n", a, b, c);

||:左右两个操作数都为假,结果为假,如果左右操作数有一个或都为真,结果一定为真


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

test:
int a=1, b=1, c;
c=a--||++b;
printf("%d %d %d\n", a, b, c);  //0 1 1

int a=1, b=1, c;
c=--a||++b;
printf("%d %d %d\n", a, b, c);  //0 2 1

!:操作数为真结果为假,操作数为假结果为真


int a=0, c=1;
c=!a; //假

 

4)位运算

& | ~ ^ << >>

&:位与,双目运算符,左右操作数按位进行与操作


char a=0x10, b=071, c;
c=a&b;
 =10000&111001
 =010000&111001
 =010000
 =0x10

|:位或,双目运算符,左右操作数按位进行或操作


char a=20, b=66, c;
c=a|b;
 =10100|1000010
 =0010100|1000010
 =1010110
 =64+16+4+2
 =86

~:位反,单目运算符,把操作数按位取反


char a=77, c;
c=~a;
 =~(64+8+4+1)
 =~1001101
 =0110010
 =32+16+2
 =50

^:异或,双目运算符,左右操作数按位进行异或(相同为0不同为1)操作


char a=14, b=51, c;
c=a^b;
 =1110^110011
 =001110^110011
 =111101
 =32+16+8+4+1
 =61
test:
//只能针对整数
int a=3, b=4;   
a^=b->a=a^b->a=011^100->a=111
b^=a->b=b^a->b=111^100->b=011->b=3
a^=b->a=a^b->a=111^011->a=100->a=4

a=a+b;
b=a-b;
a=a-b;

<<:左移,双目运算符,把左操作数向左移动右操作数这么多位


unsignedchara=151, c;
c=a<<3;
 = (128+16+4+2+1) <<3
 =10010111<<3
 =10111000

>>:右移,双目运算符,把左操作数向右移动右操作数这么多位


unsigned char a=151, c;
c=a>>3;
 = (128+16+4+2+1) >>3
 =10010111>>3
 =00010010

注意:

移位操作时,目标数据本身不会发生改变

有符号移位: 符号位不变,看目标数据的正负


正数:
01111111<<1->01111110
01111111>>1->00111111
负数:
10001111<<1->10011110
10001111>>1->11000111
思考:
1.如何把某个变量低4位快速清0?
    char a;
    a&0xf0; //a & (~(0xf))
    [20:23]
    b& (~(0xf<<20))
2.如何把某个变量高4位快速置1?
    char a;
    a|0xf0;
    [20:23]
    b| (0xf<<20)
5)复合赋值运算符

int a=3;
a*=3+2;
a=a* (3+2)

sizeof int

 

9.标准输入和输出函数

1)字符输出函数

putchar('A');
putchar(65);
成功返回打印字符的ASCII失败返回-1
2)字符串输出函数

puts();

参数要求是字符串的起始地址;

输出参数代表的字符串遇到'\0'停止,会在输出结束后自动添加'\n'

返回值代表实际输出的元素个数(包含'\0')

3)格式化输出函数

printf();

不定参数的函数

格式控制符和修饰符需要大家记忆。

转换说明

输出

%a,%A

浮点数、十六进制数和p-计数法(C99)

%c

一个字符

%d

有符号十进制数

%e,%E

浮点数,e计数法

%f

浮点数,十进制计数法

%g,%G

根据数值不同自动选择%f或%e,%e格式在指数小于-4或者大于等于精度时使用

%i

有符号十进制整数(与%d相同)

%o

无符号八进制整数

%p

指针

%s

字符串

%u

无符号十进制数

%x,%X

使用十六进制数0f的无符号十六进制整数

%%

打印一个百分号

prinf()修饰符

修饰符

意义

标志

五种标志将在后面的表中说明,可以使用零个或者多个标志示例: "%-10d"

digit(s)

字段宽度的最小值。如果字段不能容纳要打印的数或者字符串,系统会使用更宽的字段示例: "%4d",“%10s”

.digit(s)

精度.对于%e,%E和%f转换,是将要在小数点的右边打印的数字的位数。对于%g和%G转换,是有效数字的最大位数。对于%s转换,是将要打印的字符的最大数目。对于整数转换,是将要打印的数字的最小位数。如果必要,要使用前导0来达到位数。只使用"."表示其后跟随一个0,所以%.f和%.0f相同示例: “%5.2f”表示打印一个浮点数,它的字段宽度为5个字符,小数点后有两个数字

h

和整数转换说明符一起使用,表示一个short int或unsigned short int类型数值示例: “%hu”, "%hx", "%6.4hd"

hh

和证书转换说明符一起使用,表示一个signed char或unsigned char类型数值

j

和整数转换说明符一起使用,表示一个intmax_t或uintmax_t值示例: "%jd","%8jx"

l

和整数转换说明符一起使用,表示一个long int或unsigned long int类型值

ll

和整数转换说明符一起使用,表示一个long long int或unsigned long long int类型值(C99)示例: "%lld","%8llu"

L

和浮点数转换说明符一起使用,表示一个long double值示例: "%Lf", "%10.4Le"

t

和整数转换说明符一起使用,表示一个ptrdiff_t值(与两个指针之间的差相对应的类型)(C99)示例: "%td", "%1ti"

z

和整数转换说明符一起使用,表示一个size_t值(sizeof返回的类型)(C99)示例: "%zd","%12zx"

printf()的标志

标 志

意义

-

项目左对齐,即,会把项目打印在字段的左侧开始处示例: "%-20s"

+

有符号的值若为正,则显示带加号的符号;若为负,则显示带减号的符号示例: "%+6.2f"

(空格)

有符号的值若为正,则显示时带前导空格(但是不显示符号);若为负,则带减号符号。+标志会覆盖空格标志示例: "% 6.2f"

#

使用转换说明的可选形式。若为%o格式,则以0开始;若为%x和%Xgeshi ,则以0x或0X开始。对于所有的浮点形式,#保证了即使不跟任何数字,也打印一个小数点字符。对于%g和%G格式,它防止尾随0被删除示例: "%#o", "%#8.0f", "%+#10.3E"

0

对于所有的数字格式,用前导零而不是空格填充字段宽度。如果出现-标志或者指定了精度(对于整数)则忽略该标志示例: "%010d", "%08.3f","%02X"

4)字符输入函数

getchar();

无参数

从标准输入中获取一个字符,并通过返回值返回其ASCII

阻塞的函数

5)字符串输入函数

gets();
chara[100];
gets(a);
6)格式化输入函数

scanf();

结束标志: 空格 回车 tab

解决脏字符方法: ①getchar() ②%*c

7)Printf、scanf等语句运用

10.控制语句

1)if语句(条件语句)

简化的形式(如果不加{},则条件只包含一句)


if(条件表达式)
{
    语句1;
    ...
    语句1;
}

阶梯形式:


if(){

}elseif(){

}elseif(){

}else{

}

嵌套式:


if(){
    if(){
    }else{
    }
}else{
    if(){
    }else{
    }
}
2)分支语句:switch

switch(整数或整数表达式){
        case常量或常量表达式1:
            语句;
            break;
        case常量或常量表达式2:
            语句;
            break;
        case常量或常量表达式3:
            语句;
            break;
        ...
        case常量或常量表达式n:
            语句;
            break;
        default:
            语句;
            break;
    }
3)循环语句:for

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

循环体;

}

执行此次循环时,表达式1只会执行1次;

先执行表达式2,为真则执行循环体,为假退出循环;

循环体执行完成,执行表达式3 表达式3执行完成,再执行表达式2。


for(int i=0; i<10; i++);
​
注:
    表达式都可以省略for(;;); //代表死循环
嵌套for循环:
for(表达式1;表达式2;表达式3){
    for(表达式4;表达式5;表达式6){
        循环体;
    }
}
1、执行表达式1
2、执行表达式2,表达式2为假就退出循环,为真继续执行
3、表达式4
4、执行表达式5,表达式5为假,执行表达式3
    再执行表达式2,表达式2为假退出循环
    为真继续执行第3步
5、执行循环体
6、执行表达式6
7、跳到第4步执行
 
    
for(int i=0; i<3; i++){
    for(int j=0; j<3; j++){
        printf("i:%d j:%d\t", i, j);
    }
    puts("");
}
i:0j:0    i:0j:1    i:0j:2
i:1j:0    i:1j:1    i:1j:2
i:2j:0    i:2j:1    i:2j:2   
4)while循环

while(表达式1){
        循环体;
    }
表达式1为真则执行循环体,然后继续执行表达式1...为假退出while循环
    
while(1);//死循环
​
do{
    循环体;
}while(条件表达式)
先执行一次循环体,再执行条件表达式,为真继续执行循环体...为假退出循环
5)goto跳转语句

它只能在同一个函数内部跳转


int main()
    {
        puts("111111111");
        puts("222222222");
        gotoxxx;
        puts("333333333");
        xxx:
        puts("444444444");
    }

 

6)辅助控制语句

break: 跳出本层循环或者switch语句

 

continue:结束本次循环直接进入下一次循环

 

 return:函数返回

三、数组


数组:同种数据类型的有序集合

1.数组的一般形式

<存储类型> <数据类型> <数组名>[常量或常量表达式]; //[]变址运算符


int arr[10];  //在内存中开辟10个元素空间,每个空间都是一个int变量
2.初始化
1)完全初始化

在数组定义时,将每个元素都赋值


int arr[5] = {1,2,3,4,5};
2)部分初始化

在数组定义时,将部分数据初始化,其它的数据会被系统自动初始化为0


char arr[5] = {'a','b'};
int arr[10] = {0}; //定义时清空数组
3)下标法初始化

在数组定义时,指定下标赋值,其它的数据会被系统自动初始化为0


short arr[10] = {[1] =10, [7] =20};

数组具备自动计数的功能: (数组必须要初始化)


int arr[] = {1,2,3,4};    //系统会自动根据初始化元素个数决定给该数组分配多大空间        
如上所示:系统会给该数组分配4*sizeof(int)字节
C语言对数组不做越界检查:
    C语言编译器在编译程序不对数组做越界检查,所以我们在使用数组时需要注意不要越界
int arr[5] = {1,2,3,4,5};
arr[10] =10;       //语法不会报错,但是这个数据越界了
4)注意
①数组在定义时,下标代表元素的个数N(从1开始计数),
②在数组使用时数组的下标是从0开始,所以在使用时下标的范围是0 ~ N-1

3.可变数组

数组下标是一个变量,且不允许初始化


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

4.数组的赋值

只能对数组元素一个一个的赋值;


int arr[5];
arr[0] =0;
arr[1] =1;
...
arr[4] =4;
//arr[5] = {0,1,2,3,4};  //err

5.数组的遍历

通过循环实现数组的遍历


int arr[5] = {1,2,3,4,5};
for(int i=0; i<5; i++){
    printf("%d ", arr[i]);
}
puts("");
​
数组元素从键盘录入:
int arr[5] = {1,2,3,4,5};
for(int i=0; i<5; i++){
    scanf("%d", &arr[i]);
}
示例1:实现数组的逆序

#include <stdio.h>
​
intmain(int argc, char* argv[])
{ 
    int n;
    printf("input num: ");
    scanf("%d", &n);   //通过键盘输入
    int a[n];
    for(int i=0; i<n; i++){
        printf("please input a[%d]: ", i);
        scanf("%d", &a[i]);   //对下标进行遍历输入
    }
​
    for(int i=0; i<n; i++)
        printf("%d ", a[i]);
        puts("");
​
    for(int i=0; i<n/2; i++){    //实现数组的逆序
        a[i] ^=a[n-i-1];
        a[n-i-1] ^=a[i];
        a[i] ^=a[n-i-1];
    }
​
    for(int i=0; i<n; i++)    //遍历输出
        printf("%d ", a[i]);
        puts("");
    return 0;
}   
示例2:移位

#include <stdio.h>
​
intmain(int argc, char*argv[])
{ 
    int a[5] = {1,2,3,4,5}, tmp;
    for(int i=0; i<5; i++)
        printf("%d ", a[i]);
    puts("");
​
    //1、用tmp保存最后一个元素的值
    tmp=a[4];
    //2、将数组元素依次向右移动1位
    for(int i=4; i>0; i--){
        a[i] =a[i-1];
    }
    //3、将tmp保存的值赋值给a[0]
    a[0] =tmp;
    for(int i=0; i<5; i++)
        printf("%d ", a[i]);
    puts("");
​
    return 0;
} 

6.字符数组


charstr[20] ="hello world";
charstr[20] = {'h','e','l'};
    
gets(str); //scanf("%s", str);  
1)字符串

一串单个的字符的集合 "abcd" 字符串末尾一定有'\0' 用字符数组存储字符串


char a[] ="abc"; sizeof a==4;

//char a[4] = "abcd"; //err

char a[20];
//a[0] = "你"; //err
char b[] ="你";
for(int i=0; b[i]; i++)
    a[i] =b[i];
2)有效字符

字符串中第一个'\0'之前的字符叫做有效字符


chara[] = {'a','b','c','\0','d','c','\0'};
sizeof a==7;
puts(a); //abc就是有效字符
3)字符串处理函数
①字符串长度(strlen)

#include <string.h>
size_t strlen(const char*s);
const char*s: 要求字符串长度的目标字符串(传递一个字符串的首地址)
size_t: 目标字符串的有效长度  
(在32位系统中size_t代表unsigned int类型; 在64位系统中size_t代表long unsigned int类型)
②字符串的拷贝(strcpy)

有a,b两个字符串,把b字符串的内容拷贝a
#include <string.h>
实现字符串的完全拷贝
char* strcpy(char*dest, const char*src);

char*dest: 要拷贝的字符串的首地址
const char*src: 要拷贝的目标字符串(被拷贝的字符串)
char*: 返回要拷贝的字符串的首地址
注意:
        第一个参数不能是字符串常量,只能是一个字符数组或malloc开辟堆空间的首地址        
第一个参数所在空间的大小应该大于第二个参数代表字符串的有效长度,否则会访问越界
③字符串部分拷贝函数(strncpy)

拷贝第二个参数代表字符串的前n个字符到第一个参数所带表的空间
char*strncpy(char*dest, const char*src, size_t n);
size_t n: 要拷贝目标字符串的前n位
④字符串拼接函数(strcat和strncat)

char*strcat(char*dest, const char*src);

char*dest:要拼接的第一个字符串首地址
const char*src:要拼接的第二个字符串首地址
char*:返回拼接后字符串的首地址
注意: 第一个参数不能是字符串常量,只能是一个字符数组或malloc开辟堆空间的首地址        
第一个参数所在空间的大小应该大于或等于两个字符串有效字符之和
把第二个字符串前n个字符拼接在第一个字符串之后
char*strncat(char*dest, const char*src, size_t n);
⑤字符串的比较(strcmp和strncmp)

比较两个字符串是否完全一致(比较两个字符串的有效字符,不会改变两个字符串)
int strcmp(const char*s1, const char*s2);
返回值: 
    0表示两个字符串完全一致
    非0: 两个字符串不同

比较两个字符串的前n个字符
int strncmp(const char*s1, const char*s2, size_t n);

7.冒泡排序


int a[5] = {3,1,2,7,6};
第一步:
    找出最大的元素并且把它放到数组最后(从小到大):
        a[0] >a[1]  交换a[0] 和a[1]的值
        for(int j=0; j<5-1; j++)
        {
            if(a[j] >a[j+1])
            {
                a[j] ^=a[j+1];
                a[j+1] ^=a[j];
                a[j] ^=a[j+1];
            }
        }
第二步:
    继续找出第二大的数:
        重复第一步的过程:
for(int i=0; i<N-1; i++)  //代表步数
{
    for(int j=0; j<N-1-i; j++)
    {
        if(a[j] >a[j+1])
        {
            a[j] ^=a[j+1];
            a[j+1] ^=a[j];
            a[j] ^=a[j+1];
        }
    }
}
示例:输入一个无序字符串,将其进行冒泡排序:

#include <stdio.h>
#include <string.h>
​
int main()
{
    char str[100];
    printf("please input string: ");
    gets(str);
    int n=strlen(str);
    
    for(int i=0; i<n-1; i++)
    {
        for(int j=0; j<n-1-i; j++)
        {
            if(str[j] >str[j+1])
            {
                str[j] ^=str[j+1];
                str[j+1] ^=str[j];
                str[j] ^=str[j+1];
            }
        }
    }
    
    puts(str);
}

四、二维数组


二维数组:有两个下标的数组

1.一般形式

<存储类型> <数据类型> <数组名>[行标]*[列标];

2.初始化
1)完全初始化

int a[2][3] = {1,2,3,4,5,6};
int b[2][3] = {{1,2,3},{4,5,6}};
2)不完全初始化

int a[2][3] = {1,2,3}; //第二行补0
int b[2][3] = {0}; //均为0
3)自动计数功能

int a[][3] = {1,2,3,4,5,6,7,8}; //int a[3][3],只能省略行标不能省略列标
int b[][3] = {1,2,3,4};
3.二维数组的遍历

int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
for(int i=0; i<3; i++) //外层循环表示行,内层循环表示列
{
    for(int j=0; j<4; j++)
    {
        printf("%d ", a[i][j]);
    }
    puts("");
}
4.示例
1)从键盘录入一个3行4列的二维数组,并且打印其最大的元素和对应下标.

int main(int argc, char*argv[])
{ 
    int a[3][4], max, x=0, y=0;
    for(int i=0; i<3; i++)
    {
        for(int j=0; j<4; j++)
        {
            printf("input a[%d][%d]: ", i, j);
            scanf("%d", &a[i][j]);
        }
    }
​
    max=a[0][0];
    for(int i=0; i<3; i++)
    {
        for(int j=0; j<4; j++)
        {
            if(max<a[i][j])
            {
                max=a[i][j];
                x=i;
                y=j;
            }
        }
    }
​
    printf("max:%d x:%d y:%d\n", max, x, y);
    return 0;
} 
2.打印杨辉三角.//做这类题首先得找到规律,并提前写出

1

1 1

1 2 1

1 3 3 1

1 4 6 4 1

1 5 10 10 5 1

....


a[i][0] ==1   a[i][i] ==1
i>1, j>0&&j<i
a[i][j] =a[i-1][j] +a[i-1][j-1]

#include <stdio.h>
​
int main(int argc, char*argv[])
{ 
    int n;
    puts("input num: ");
    scanf("%d", &n);
    int a[n][n];
    for(int i=0; i<n; i++)
    {
        a[i][0] =1;
        a[i][i] =1;
    }
​
    for(int i=2; i<n; i++)
    {
        for(int j=1; j<i; j++)
        {
            a[i][j] =a[i-1][j] +a[i-1][j-1];
        }
    }
​
    for(int i=0; i<n; i++)
    {
        for(int j=0; j<=i; j++)
        {
            printf("%-5d", a[i][j]);
        }
        puts("");
    }
    return 0;
} 
用while循环实现数组数据的录入:
    inta[3][4], i=0, j=0;
    while(i<3){         
        while(j<4){   
            scanf("%d", &a[i][j]);     
            j++;
        }
        j=0;
        i++;
    }

五、二维字符数组



char str[3][10] = {"abc", "hello", "welcome"}; //字符串长度不能超过列数
char dst[3][10];
键盘录入:
gets(a[i]);

六、指针


        在计算机内部存储器(内存)中,以字节为单位,每个字节都有一个编号,这个编号叫做地址。指针就是地址。

 

1.指针变量

指针变量:存储地址的变量

普通变量:存储的是数据

第一类:创建指针变量,“ 基本类型+ * ”。


int* iptr;
char* cptr;
double* dptr;

第二类:给指针变量赋值,“ &+变量 ”。


int a=100;
char b='o';
double c=100.5;
​
int* iptr;
char* cptr;
double* dptr;
​
iptr=&a;
cptr=&b;
dptr=&c;

第三类:取指针变量保存的地址中的值,“ * + 指针变量”。


int a=100;
int*iptr=&a;
int b=*iptr;
    于是,上面的代码改写成:
void swap(int*x, int*y)
{
    int tmp=*x;
    *x=*y;
    *y=tmp;   
}
void change()
{
    int a=100;
    int b=200;
    swap(&a, &b);
}

地址、指针变量都叫指针

2.一般形式

<存储类型><数据类型>*<指针变量名>


int*pa(野指针:不知内容是什么)

3.初始化和赋值

1)初始化

int a=10;
int*pa=&a;
char*ps=NULL;//NULL空指针 NULL是0的宏  //char是字符变量或者字符串
2)赋值

double a=3.14;
double*pa;
pa=&a;

int a=10, b=20;
int*p=&a;
p=&b; //更改指针变量P的指向 指针变量是可改变的

int a=10 , b=20;
int*p;
p=&a; //a的地址给了p 

*p==a==10
a=15;
printf("%d %d\n",a,*p);  //a = 15  *p = 15
*p=30;
printf("%d %d\n",a,*p);  //a = 30 *p = 30

注意:在使用指针时,当脱离定义后,指针变量无表示的是地址,有表示具体的数据

4.指针支持的运算

1)算术运算:+ - ++ --

px+n :结果表示从px存储的地址开始向地址大的方向偏移n个元素

        偏移n*sizeof(指针指向的数据类型)字节

px-n :结果表示从px存储的地址开始向地址小的方向偏移n个元素

        偏移n*sizeof(指针指向的数据类型)字节

px++ :px向地址大的方向偏移一个元素,且px指向这个新(偏移后)的地址

px-- :px向地址小的方向偏移一个元素,且px指向这个新(偏移后)的地址

Px-py: px和py指向变量的类型必须一致,结果表示两个指针之间间隔元素的个数(间隔字节/sizeof(元素的类型))

表达式

含义

p++或(p++)

首先计算表达式的值为*p,然后p自增1

(*p)++

首先计算表达式的值为p,然后p自增1

++p或(++p)

首先将p自增1,然后计算表达式的值为*p

++p或++(p)

首先将(p)自增1,然后计算表达式的值为(p)

2)关系运算:> < >= <= == !=

关系运算比较的实质是地址编号的大小

3)逻辑运算:&& || !

非NULL为真

4)注意:不支持位运算

5.指针和一维数组

1)指针和一维数组的关系

char a[10] ="abcdef";//位数是从0开始,加1即为'b'
char*p=&a[0];
*p=='a';
*(p+1) =='b';
*(p+3) =='d';
2)指针遍历一维数组

int a[5] = {1,2,3,4,5}, *p=&a[0];
for(int i=0;i<5;i++);
    printf("%d", *(p+i));

int a[4] = {1,2,3,4}, *p ,*q;
p=q=a;
q++;
printf("%d\n", q-p);   //=1
printf("%d\n",(char*)q-(char*)p);//4

*和[]等价
*和&互为逆运算
[]和&互为逆运算

int a[10], *p=a;
0=<i<10
a[i] ==*(a+i) ==*(p+i) ==p[i]

int a[5];

6.行指针


int (*a)[5];
//指向数组(这里每个一维数组含5个元素)的指针,a是第一个一维数组的首元素地址,a+1指向第二个一维数组的首元素地址......(a是数组指针);

7.数组指针

本质上是一个指针,只不过这个指针指向的是整个数组

1)一般形式

<存储类型> <数据类型> (*指针名)[下标];


char (*p)[3];   //下标代表该指针可以指向具备该下标这么多个元素的数组

int a[4] = {1,2,3,4},b[4] = {4,3,2,1};
int (*p)[4] =&a;

p=&b; 
2)遍历一维数组

for(int i=0; i<4; i++)
printf("%d ", (*p)[i]); // *p == *&b --> *p == b  -->  (*p)[i] == b[i]

注意: 对一维数组的数组名取地址表示整个一维数组的地址

3)指向二维数组

int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
int (*p)[4];
p=a;
4)遍历整个二维数组

for(inti=0; i<3; i++){
        for(intj=0; j<4; j++)
            printf("%d ", (*(p+i))[j]);  //p[i][j] *(*(p+i)+j)
        puts("");
    }

8.指针数组

本质是一个数组,只不过这个数组里面每一个元素都是指针

1)一般形式

<存储类型> <数据类型> *数组名[下标];


int*a[3];   //代表数组a中有3个int *的指针

int a=10, b=20, c=30;
int* arr[3] = {&a, &b, &c};
*arr[0]  ==a  ==10;

char a[100] ="one", b[100] ="world!", c[100] ="welcome";
char*arr[3] = {a, b, c};
*(arr[0]+1) ==a[1] =='n';
*arr[0] ='N'; //OK  a[0] = 'N';

char*p="hello";

char*arry[4] = {"abc", "defg", "bbbbb", "cccccc"};
puts(arry[0]);
*(arry[0]+1) =='b';
//*arry[0] = 'A'; //err
2)和二维数组联系

int a[2][3] = {1,2,3,4,5,6};
int*arr[2] = {a[0], a[1]};
3)遍历

for(inti=0; i<2; i++){
        for(intj=0; j<3; j++)
            printf("%d ", *(arr[i]+j));  //arr[i][j]   *(*(arr+i)+j)  (*(arr+i))[j]
        puts("");
    }
4)示例:通过指针数组计算对应二维数组所有元素的累加和

#include <stdio.h>
//声明函数
//a, b是形参
int add(inta, intb);
​
int main(int argc, char*argv[])
{ 
    //int a = 1, int b = 2;
     //1,2是实参
    int sum=add(1,2);
    return 0;
} 
​
//函数的实现
int add(int a, int b)
{
    //int sum = a+b;
    //return sum;
    return a+b;
}
5)数组指针与指针数组区别

9.二级指针

指向指针的指针,一个二级指针变量可以存放一级指针的地址

1)一般形式

<数据类型> **二级指针名;


int**p;

int a=10; 
int*pa=&a;

int**pp;
pp=&pa;

pp==&a  -->*pp==*&pa==pa==&a-->**pp==**&pa==*pa==*&a==a

二级指针和二维数组的关系?
没有关系!!!

七、函数


具有特定功能的代码模块

1.一般形式

<存储类型> <数据类型> 函数名(形式参数列表)

{

        函数体;

        return;

}

1)函数的声明

void ptr(void);  //一般放在文件的开头(main函数之前)
2)函数调用

ptr();           //在需要实现对应功能的地方调用函数
3)函数的实现

void ptr(void)  //一般在main函数之后
{
    puts("你睡醒了吗?");
}

总结:

        当实参不希望被被调函数修改的时候,可以使用函数的参数传递

        当实参希望被被调函数修改的时候,要使用函数参数的地址传递

2.函数参数是一维数组

        也分为值传递和地址传递(本质上都是地址的传递,所以不能通过sizeof(数组名)/sizeof(数组某个元素)来计算数组元素的个数)


void fun(inta[], intn);
void fun(int*a, intn);

        在给函数传递字符串时,只需要传递首地址(字符串末尾有'\0')函数中定义的变量它的作用域在函数内部,生命周期开始于函数调用,结束于函数调用结束

3.函数参数是二维数组


void fun(int n, int m, int a[][n]);   
void fun(int n, int m, int (*a)[n]);
//void fun(int n, int m, int **a);   //**a是二级指针 传值时不能传递二维数组

4.指针函数

本质上是一个函数,只不过返回值是一个指针

1)一般形式

<数据类型> *<函数名>(形参列表) {

        函数体;

        return 地址;

}不能返回局部变量的地址

注意:函数的地址就是函数名

5.函数指针

本质上是一个指针,只不过这个指针指向的是一个函数

1)一般形式

<数据类型> (*<函数指针名>)(形参列表);


int add(int a, int b);
int (*p)(int, int);   //p就是一个指向返回值为     int且参数有两个int类型的函数
2)static

1、延长变量的生命周期;

2、限制作用域;

3、修饰的变量只能被初始化1次

4、修饰的变量如果没有初始化,会被自动初始化0

3)const(常量化关键字)

const int a=10;   //修饰变量时表示这个为只读(常量化)


int val1=10, val2=20;
const int*p=&val1;   //常量化指针,不能通过该指针去修改指向空间的内容
p=&val2               //但是可以修改指向
int const*q=&val1;   //同上

int*const po=&val1; //指针的指向不可以发生改变,但是可以通过该指针修改对应空的内容

//指向和内容都不允许修改
const int*const pp=&val1; 

void*:
代表任意类型的指针,不支持+、-

6.递归函数

自己调自己


voidfun(charch)
{
    if(ch<'D'){
    printf("%c\n",ch);
    fun(ch+1);
    printf("%c\n",ch);
    }
        return;
}
​
intmain()
{
fun('A');
}
​
​
intfun(intn)
{
    if
    
    }

八、结构体


不同数据类型的集合

1.一般形式

struct 结构体名{

数据类型1 变量名1;

数据类型2 变量名2;

...

数据类型n 变量名n;

};

struct 结构体名 变量名;


struct worker{
        char name[20];
        int id;
        double salary;
    };
    struct workerboss= {"张三",9527,1.7};
    //boss.name = "小红";// err
    strcpy(boss.name,"小红");
    boss.id=9528'
    
    printf("name:%s\n",boss.name);//s是字符串
    
    struct worker*p=&boss;
    p->id=9529;
    
    struct workerarr[10] ={0};
    scnaf("%s %d %f",a[0].name,&(arr[0].id),&(arr[0].salary));

九、共用体/联合体(union)


共用一个空间


union XXX{
    chara;
    intb;
};
union xxa;
a.a='A';
printf("%c \n",a.b);//得A

十、内存管理(malloc/free)


  1. 开辟空间的函数


malloc        int p = malloc(100sizeof(int))

2.释放空间的函数:


free(p);
char*fun(char*p)
    {
        char*q=malloc(100);
        strcpy(q,p);
        returnq;
    }
    intmain()
    {
        char*p="hello world";
        char*q=fun(p);
        puts(q);
        free(q);
    }
    
    voidGetMem(char*p)
    {
        *p=malloc(100);
    }
    
    intmain()
    {
        char*p;
        GetMem(p);
        strcpy(p ,"hello");
        puts(p);
        free(p);
    }     //程序的问题:实参与形参的区别,实参应该是指针的地址
  // 地址与值的传递
实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量-->>
在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。-->>
因此应预先用赋值,输入等办法使实参获得确定值

3.实参与形参

实参和形参的数据传递方式有两种:

        一种是值传递,实参和形参都不是指针,这种情况下函数调用中发生的数据传送是单向的。 即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。在函数调用过程中,形参的值发生改变,而实参中的值不会变化。究其原因,是因为这两个参数在内存中位于不同的位置,值传递只是形参将实参的内容复制,在内存中被分配了新的内存单元,在函数执行完毕以后地址会立刻被释放掉,因此形参的改变不会对实参有任何影响。

        另一种是地址传递,就是实参与形参共用同一个内存单元,在函数执行的过程中,实际就是对实参的地址进行操作,因此形参改变,实参同步变化,实际他们就是同一变量,因为在内存中占据的就是一个内存单元。

十一、浮点数的存储



浮点数的存储:
    浮点数的二进制:
        分成两部分:
            整数部分:2的整数次幂相加
            小数部分:小数部分不断乘2取整
            
浮点数在存储时先转化成2进制,再将二进制写成科学计数法-->>1.10010*2^1
浮点数在内存中占4字节32位,最高为表示符号位,其次8位表示指数位,剩下的表示小数位,整数部分少1
void swap(char**sa, char**sb)
{
    char*t;
    t=*sa;
    *sa=*sb;
    *sb=t;
}
​
int main()
{
    char*pa="abc";
    char*pb="ABC";
    swap(&pa, &pb);
}
​
3.14:
整数部分: 11
小数部分: 0.14*2=0.280.28*2=0.560.56*2=1.120.12*2=0.24...
        .0010
故为:  11.0010
浮点数在存储时先转化成2进制,在将二进制写成科学计数法
1.10010*2^1
浮点数在内存中占4字节32位,最高位表示符号其次8位表示指数位剩下的表示小数位
01000000010010001111010111000010
1.10010001111010111000010*2^1
11.0010001111
3.14

8.5:
    1000
    1
   1000.1
   1.0001*2^3
   01000001000010000 ...
   1.0001*2^3

十二、sizeof(关键字)

十三、练习(见下一篇)

C语言基础练习题总结(嵌入式系列学习2)-CSDN博客

  • 57
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式-箫声.

你的鼓励就是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值