博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

C语言笔记完整版

Posted on 2026-04-22 19:38  打工的外星人  阅读(4)  评论(0)    收藏  举报
%c\n    打印字符换行
%d\n    打印整数十进制(有符号的)
%lf\n    打印浮点数
%p\n    以地址形式打印
%x\n    打印十六进制数
%s\n    打印字符串 
%u\n    打印无符号的十进制数
//计算n的阶乘
#include<stdio.h>
int main()
{
    int i=0;
    int n=0;
    int ret=1;
    scanf("%d",&n);
    for(i=1;i<=n;i++)
    {
        ret=ret*i;
    }
    printf("ret=%d\n",ret);
    return 0;
}
//计算1!+2!+3!+4!+...10!的阶乘
#include<stdio.h>
int main()
{
    int i=0;
    int n=0;
    int ret=1;
    int sum = 0;
    //1+2+6=9
    for(n=1;n<=10;n++)
    {
        ret=1;
        for(i=1;i<=n;i++)
        {
            ret=ret*i;
            //n的阶乘
        }
        sum=sum+ret;
    }
    printf("sum=%d\n",sum);
    return 0;
}
#include<stdio.h>
int main()
{
    int arr[]={1,2,3,4,5,6,7,8,9,10};
    int k=7;
    //写一个代码,在arr数组中(有序的)中找到7
    int i=0;
    int sz=sizeof(arr)/sizeof(arr[0]);
    for(i=0;i<sz;i++)
    {
        if(k==arr[i])
        {
            printf("找到了,下标是:%d\n",i);
            break;
        }
    }
    if(i==sz)
        printf("找不到\n");
    return 0;
    
}
#include<stdio.h>
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 left=0;//左下标
    int right=sz-1;//右下标
    while(left<=right)
    {
     int mid=(left+right)/2;
     if(arr[mid]>k)
     {
        right=mid-1;
     }
     else if(arr[mid]<k)
     {
        left=mid+1;
     }
     else
     {
        printf("找到了,下标是:%d\n",mid);
        break;
     }
    }
    if(left>right)
    {
        printf("找不到\n");
    }
    return 0;
}
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <stdlib.h>
int main()
{
    //welcome to bbb!!!!!!!!!!
    char arr1[]="welcome to bbb!!!!!!";
    char arr2[]="####################";
    int left =0;
    //int right=sizeof(arr1)/sizeof(arr[0]-2);//err
    int right=strlen(arr1)-1;
    
    while(left<=right)
    {
        arr2[left]=arr1[left];
        arr2[right]=arr1[right];
        printf("%s\n",arr2);
        //休息一秒
        Sleep(1000);
        system("cls");//执行系统命令的一个函数-cls-清空屏幕
        left++;
        right--;
    }
    printf("%s\n",arr2);
    return 0;
}
#include <stdio.h>
#include <string.h>
#include <windows.h>
#include <stdlib.h>
int main()
{
    int i=0;
    char password[20]={0};
    for(i=0;i<3;i++)
    {
        printf("请输入密码:\n");
        scanf("%s",password);
        if(strcmp(password,"123456")==0)
        //==不能用来比骄傲字符串是否相等,应该使用一个库函数-strcmp
        {
            printf("密码输入正确!!1\n");
            break;
        }
        else
        {
            printf("密码错误\n");
        }
    }
    if(i==3)
               printf("三次密码均输入错误,推出程序\n");
    return 0;
}
//比较三个值的大小,按从大到小
#include <stdio.h>
int main()
{
    int a=0;
    int b=0;
    int c=0;
    scanf("%d%d%d",&a,&b,&c);
    if(a<b)
    {
        int tmp=a;
        a=b;
        b=tmp;
    }
    if(a<c)
    {
        int tmp=a;
        a=c;
        c=tmp;
    }
    if(b<c)
    {
        int tmp=b;
        b=c;
        c=tmp;
    }
    printf("%d %d %d\n",a,b,c);
    return 0;
}
//一百以内取3的倍数
#include <stdio.h>
int main () {
    int i=0;
    for(i=1;i<100;i++)
    {
        if(i%3==0)
        printf("%d\n",i);
    }
    return 0;
}
//求两数之间的最大公约数
#include <stdio.h>
int main ()
 {
     int m=1050;
     int n=500;
     int r=0;
     scanf("%d%d",&m,&n);
     while(r=m%n)
     {
         //r=m%n;
         m=n;
         n=r;
     }
     printf("%d\n",n);
    return 0;
}
//判断是否为闰年
#include <stdio.h>
int main()
{
    int year=0;
    int count=0;
    for(year=2000;year<=3000;year++)
    {
        //判断year是否是闰年
        //1.能被4整除并且不能被100整除的是闰年
        //2.能被400整除的是闰年
              
        // if(year%4==0 && year%100!=0)
        // {
        //     printf("%d\n",year);
        //     count++;
        // }
        // else if(year%400==0)
        // {
        //     printf("%d\n",year);
        //     count++;
        // }
        if((year%4==0) && (year%100!=0) || (year%400==0))
        {
           printf("%d\n",year);
           count++;
        }
    }
    printf("\ncount=%d\n",count);
    return 0;
}
#include <stdio.h>
int main()
{
    int i=0;
    int count=0;
    for(i=100;i<=200;i++)
    {
        //判断i是否为素数
        //素数判断规则
        //1.试除法
        //产生2->i-1
        int j=0;
        for(j=2;j<i;j++)
        {
            if(i%j==0)
            {
                break;
            }
        }
        if(j==i)
        {
            count++;
            printf("%d ",i);
        }
    }
    printf("\ncount=%d\n",count);
    return 0;
}
//求素数,一个因子小于开平方值
#include <stdio.h>
#include <math.h>
int main()
{
    int i=0;
    int count=0;
    //sqrt-开平方的数学库函数
    for(i=100;i<=200;i++)
    {
        //判断i是否为素数
        //素数判断规则
        //1.试除法
        //产生2->i-1
        int j=0;
        for(j=2;j<=sqrt(i);j++)
        {
            if(i%j==0)
            {
                break;
            }
        }
        if(j>sqrt(i))
        {
            count++;
            printf("%d ",i);
        }
    }
    printf("\ncount=%d\n",count);
    return 0;
}
//编写一个代码数一下1到100带9的数
#include <stdio.h>
int main () {
    int i=0;
    int count=0;
    for(i=1;i<100;i++)
    {
        if(i%10==9)
        count++;
        if(i/10==9)
        count++;
        //9,19...99模10余9
        //90,91...99除10商9
    }
    printf("count=%d\n",count);
    return 0;
}
//分数求和 1/1-1/2+1/3-1/4+1/5-......+1/99-1/100
#include <stdio.h>
int main () {
   int i=0;
   int flag=1;
   double sum=0.00;
   for(i=1;i<=100;i++)
   {
       sum+=flag*1.0/i;
       flag=-flag;
   }
   printf("%lf\n",sum);
    return 0;
}
//求最大值
#include <stdio.h>
int main () {
    int arr[]={-1,2,3,4,5,6,7,8,9,-10};
    int max=arr[0];
    int i=0;
    int sz=sizeof(arr)/sizeof(arr[0]);
    for(i=0;i<sz;i++)
    {
        if(arr[i]>max)
        {
            max=arr[i];
        }
    }
    printf("max=%d\n",max);
   return 0;
}
//在屏幕上输出9*9乘法口诀表
#include <stdio.h>
int main()
{
    int i=0;
    for(i=1;i<=9;i++)
    {
        //打印一行
        int j=1;
        for(j=1;j<=i;j++)
        {
        printf("%d*%d=%-2d ",i,j,i*j);
        }
        printf("\n");
    }
   
    return 0;
}
时间戳:当前计算机的时间-计算机的起始时间(1970.1.1 0:0:0)=(xxx)秒
//猜数游戏
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void menu()
{
    printf("**************************\n");
    printf("**** 1.play    0.exit ****\n");
    printf("**************************\n");
}
//RAND_MAX-32767
void game()
{
    //1、生成一个随机数
    int ret=0;
    int guess=0;//接收猜的数字
    //拿时间戳来设置随机数的生成起始点
    //time_t time(time_t *timer)
    //time_t
    ret=rand()%100+1;//生成1-100之间的随机数
    //printf("%d\n",ret);
    while(1)
    {
        printf("请猜数字:>");
        scanf("%d\n",&guess);
        if(guess>ret)
        {
            printf("猜大了\n");
        }
        else if(guess<ret)
        {
            printf("猜小了\n");
        }
        else
        {
            printf("恭喜你猜对了!\n");
            break;
        }
    }
}
int main () {
    int input=0;
    srand((unsigned int)time(NULL));
    do
    {
    menu();
    printf("请选择>:");
    scanf("%d",&input);
    switch(input)
        {
        case 1:
            game();//猜猜数游戏
            break;
        case 2:
            printf("退出游戏\n");
            break;
        default:
            printf("选择错误\n");
            break;
        }
    }while(input);
    return 0;
}
goto语句
反复横跳
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
    char input[20]={0};
   //shutdown -s -t 60
   //system() -执行系统命令的
   system("shutdown -s -t 60");
again:
   printf("请注意!你的电脑在1分钟内关机,如果输入:我是跌,就取消关机\n请输入>:");
   scanf("%s",input);
   if(strcmp(input,"我是爹")==0)//比较两个字符串-strcmp()
   {
       system("shutdown -a");
   }
   else
   {
       goto again;
   }
   return 0;
}

函数是什么?

    在计算机科学中,子程序(英语:Subroutine,procedure,function,routine,method,subprogram,callable unit)是一个大型程序中的某部分代码,又一个或多个语句块组成,它负责完成某项特定任务,而且相比较于其他的代码,具备相对的独立性。
    一般会有输入参数并有返回值,提供特定过程的封装和细节的隐藏。这些代码通常被集成为软件库。
#include<stdio.h>
int Add(int x,int y)
{
    int z=0;
    z=x+y;
    return z;
}
int main()
{
    int a=10;
    int b=20;
    int sum=Add(a,b);
    printf("sum=%d\n",sum);
    return 0;
}

c语言中函数分类:

    1.库函数    www.cplusplus.com
    2.自定义函数

为什么会有库函数?

    1、我们知道在我们学习c语言编程的时候,总是在一个代码编完之后迫不及待的知道结果,想把我们这个结果打印到我们的屏幕上看看。这个时候我们会频繁使用一个功能;将信息按照一定的格式打印到屏幕上(printf)。
    2、在编程的过程中我们会频繁的做一些字符串拷贝工作(strcpy)。
    3、在编程是我们也计算,总是会计算n的k次方这样的运算(pow)。

库函数分类:

    IO函数 
    字符串操作函数(strlen) 
    字符操作函数 
    内存操作函数
    时间/日期函数 
    数学函数 
    其他库函数
#include<stdio.h>
#include<string.h>
int main()
{
    char arr1[]="bit";
    char arr2[20]="********";
    //bit\0结束的标志
    strcpy(arr2,arr1);
    printf("%s\n",arr2);
    //strcpy -string copy-字符串拷贝
    //strlen -string length -字符串长度有关
    return 0;
}
#include<stdio.h>
#include<string.h>
int main()
{
    //memset
    //memory -内存 set -设置
    char arr[]="hello world";
    memset(arr,'*',5);
    printf("%s\n",arr);
    return 0;
}

自定义函数

   函数的组成
   ret_type fun_name(para1,*)
   {
      statement;//语句项(函数体交待函数的实现)
   }
   ret_type 返回类型
   fun_name 函数名
   para1 函数参数
#include<stdio.h>
//定义函数
int get_max(int x,int y)
{
    if(x>y)
    return x;
    else
    return y;
}
int main()
{
    int a=10;
    int b=30;
    //函数的使用
    int max=get_max(a,b);
    printf("max=%d\n",max);
    return 0;
}
#include<stdio.h>
//当实参传给新参的时候
//新参是实参的一份临时拷贝
//对形参的修改是不会改变实参的
void Swap1(int x,int y)
{
    int tmp=0;
    x=y;
    y=tmp;
}
void Swap2(int* pa,int* pb)
{
    int tmp=0;
    tmp=*pa;
    *pa=*pb;
    *pb=tmp;
}
int main()
{
    int a=10;
    int b=20;
    printf("a=%d b=%d\n",a,b);
    //Swap(a,b);
    Swap2(&a,&b);
    printf("a=%d b=%d\n",a,b);
    return 0;
}

函数的参数

  实际参数(实参):
    真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,他们必须有确定的值,以便把这些值传送给形参。
  形式参数(形参):
    形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完之后就自动销毁了。因此形式参数只在函数中有效。
上面Swap1和Swap2函数中的参数x,y,px,py都是形式参数。在main函数中传给Swap1的num1,num2和传给Swap2函数的&num1,&num2是实际参数。
    形参实例化之后其实相当于实参的一份临时拷贝。

函数的调用:

  传值调用
      函数的形参和实参分别占用不同的内存块,对形参的修改不会影响实参。
  传址调用
      传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
      这种传参方式可以让函数和函数外边的变量建立真正的联系,也就是函数内部可以直接操作函数外部的变量。
判断一个数是不是素数
#include <stdio.h>
#include <math.h>
int is_prime(int n)
{
    //2->n-1
    int j=0;
    for(j=2;j<=sqrt(n);j++)
    {
       if(n%j==0)
       return 0;
    }
    return 1;
}
int main()
{
    int i=0;
    for(i=100;i<=200;i++)
    {
        //判断i是否为素数
        if(is_prime(i)==1)
        printf("%d\n",i);
    }
    return 0;
}
//写一个函数判断是否为闰年
#include <stdio.h>
int is_leap_year(int y)
{
    if((y%100!=0 && y%4==0)|| (y%400==0))
      return 1;
    else
      return 0;
}
int main()
{
    int year=0;
    for(year=1000;year<=2000;year++)
    {
        //判断year是否为闰年
        if(1==is_leap_year(year))
        {
            printf("%d  ",year);
        }
    }
    return 0;
}
//写一个函数,实现一个整形有序数列的二分查找
#include <stdio.h>
//本质上arr是一个指针
int binary_search(int arr[],int k,int sz)
{
    //算法实现
    int left=0;
    int right=sz-1;
    while(left<=right)
    {
    int mid=(left+right)/2;//中间元素的下标
    if(arr[mid]<k)
     {
        left=mid-1;
     }
    else if(arr[mid]>k)
     {
        right=mid-1;
     }
    else
     {
        return mid;
     }
    }
    return -1;
}
int main()
{
   //二分查找
   //在一个有序数组中查找具体的某个数
   //如果找到了返回,这个数的下标,找不到返回-1
   int arr[]={1,2,3,4,5,6,7,8,9,10};
   int k=11;
   int sz=sizeof(arr)/sizeof(arr[0]);
   //传递过去的是首元素的地址
   int ret=binary_search(arr,k,sz);
   if(ret == -1)
   {
       printf("找不到指定的数字\n");
   }
   else
   {
       printf("找到了,小标是:%d\n",ret);
   }
    return 0;
}
//写一个函数,每调用一次这个函数,就会将num的值增加1。
#include<stdio.h>
void Add(int* p)
{
    (*p)++;
}
int main()
{
    int num=0;
    Add(&num);
    printf("num=%d\n",num);//1
    Add(&num);
    printf("num=%d\n",num);//2
    Add(&num);
    printf("num=%d\n",num);//3
    return 0;
}

函数的嵌套调用和链式访问

嵌套调用

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

链式访问

    把一个函数返回值作为另一个函数的参数。
#include<stdio.h>
int main()
{
   int len=0;
   //1
//    len=strlen("abc");
//    printf("%d\n",len);
    //2
    printf("%d\n",strlen("abc"));
    return 0;
}
#include<stdio.h>
int main()
{
    //只打印字符个数
    printf("%d",printf("%d",printf("%d",43)));//4321
    return 0;
}

函数的声明和定义

 函数声明:
    1、告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体不是存在,无????????
    2、函数的声明一般出现在函数的使用之前,要满足先声明后使用。
    3、函数的声明一般要放在头文件中。

函数定义:

    函数的定义是指函数的具体实现,交代函数的功能实现。
#include<stdio.h>
//函数声明
int Add(int x,int y);
int main()
{
    int a=10;
    int b=39;
    int sum=0;
    //函数调用
    sum=Add(a,b);
    printf("%d\n",sum);
    return 0;
}
//函数定义
int Add(int x,int y)
{
    int z=x+y;
    return z;
}

函数的递归

什么是递归?

    程序调用自生的编程技巧成为递归(recursion)。递归作为一种算法在程序设计语言中广泛应用。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可以描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量,递归????????????????????????????????

递归的俩个必要条件

    ·存在限制条件,当满足这个限制条件的时候,递归便不再继续。
    ·每次递归调用之后越来越接近这个限制条件
程序员知乎:https://stackoverflow.com/
练习:接受一个整型值(无符号),按照顺序打印它的每一位。例如:输入1234,输出1 2 3 4
参考代码:
#include<stdio.h>
void print(int n)
{
    if(n>9)
    {
        print(n/10);
    }
    printf("%d ",n%10);
}
int main()
{
    unsigned int num=0;
    scanf("%d",&num);//1234
    //递归
    print(num);
    //print(1234);
    //print(123)4
    //print(12)3 4
    //print(1)2 3 4
    return 0;
}
#include<stdio.h>
#include<string.h>
/*int my_strlen(char* str)
{
    int count=0;
    while(*str !='\0')
    {
        count++;
        str++;
    }
    return count++;
}*/
//递归的方法
int my_strlen(char* str)
{
    if(*str !='\0')
        return 1+my_strlen(str+1);
    else
        return 0;
}
//把大事化小
//my_strlen("bit");
//1+my_strlen("it");
//1+1+my_strlen("t");
//1+1+1+my_strlen("");
//1+1+1+0
//3
int main()
{
    char arr[]="bit";
    //int len=strlen(arr);//求字符串长度
    //printf("%d\n",len);
    //模拟实现了一个strlen函数
    int len=my_strlen(arr);//arr是数组,数组传参,传过去的不是整个数组,而是第一个元素的地址
    printf("len=%d\n",len);
    return 0;
}
递归与迭代
求n的阶乘
参考代码:
#include<stdio.h>
#include<string.h>
/*int Facl(int n)
{
    int i=0;
    int ret=1;
    for(i=1;i<=n;i++)
    {
        ret *=i;
    }
    return ret;
}
int Fac2(int n)
{
    if(n<1)
        return 1;
    else
        return n*Fac2(n-1);
}
int main()
{
    //求n的阶乘
    int n=0;
    int ret=0;
    scanf("%d",&n);
    ret=Fac2(n);//循环的方式
    printf("%d\n",ret);
    return 0;
}
*/
//斐波那契数列
//1 1 2 3 5 8 13 21 55....
//描述第n个斐波那契数列的时候
int count=0;
int Fib(int n)
{
    if(n==3)//测试第3个斐波那契数的计算次数
    {
        count++;
    }
    if(n<=2)
        return 1;
    else
        return Fib(n-1)+Fib(n-2);
}
int Fib(int n)
{
    int a=1;
    int b=1;
    int c=1;
    while(n>2)
    {
        c=a+b;
        a=b;
        b=c;
        n--;
    }
    return 0;
}
int main()
{
    int n=0;
    int ret=0;
    scanf("%d",&n);
    //TDD-测试驱动开发
    ret=Fib(n);
    printf("ret=%d\n",ret);
    //printf("count=%d\n",count);
    return 0;
}

一维数组的创建和初始化

数组的创建

数组是一组相同类型元素的集合。数组的创建方式:
type_t  arr_name  [const_n];
//type_t 是指定数组的元素类型
//const_n 是一个常量表达式,用来指定数组的大小
数组创建的实例:
#include<stdio.h>
int main()
{
         //创建一个数组-存放整型-10个
         int arr[10]={1,2,3};//不完全初始化,剩下的元素默认初始化为0
         char arr2[5]={'a','b','c'};
         char arr3[5]="ab";//ok
         char arr4[]="abcdef";
         printf("%d\n",sizeof(arr4));
         //sizeof计算arr4所占空间的大小
         //7个元素char 7*1=7
         printf("%d\n",strlen(arr4));
         //strlen 求字符串的长度 -'\0'之前的字符个数
         //{a b c d e f \0}
         
         //int n=5;
         //char ch[n];//err
         return 0;
}
//1.strlen和sizeof 没有关联
//2.strlen 是求字符串长度的-只针对字符串求长度 -库函数-使用的应用头文件
//3.sizeof 计算变量、数组、类型的大小 -单位是字符 -操作符
//#include<stdio.h>
//int main()
//{
//     char arr1[]="abc";  //a b c \0
//     char arr2[]={'a','b','c'};  //a b c
//     printf("%d\n",sizeof(arr1));
//     printf("%d\n",sizeof(arr2));
//     printf("%d\n",strlen(arr1));
//     printf("%d\n",strlen(arr1));
//     return 0;
//}

一维数组的使用

对于数组的使用我们之前介绍了一个操作符:[],下标应用操作符。它其实就数组访问的操作符。我们来看代码:
#include<stdio.h>
int main()
{
    //char arr[]="abcdef";//[a][b][c][d][e][f][\0]
    //printf("%c\n",arr[3]);
    //int i=0;
    //int len=strlen(arr);
    //for(i=0;i<len;i++)
    //{
    //  printf("%c ",arr[i]);
    //}
    int arr[]={1,2,3,4,5,6,7,8,9,0};
    int sz=sizeof(arr)/sizeof(arr[0]);
    int i=0;
    for(i=0;i<sz;i++)
    {
        printf("%d ",arr[i]);
    }
    return 0;
}
总结:
  1. 数组是使用下标来访问的,小标从0开始。
  2. 数组的大小可以通过计算得到

一维数组在内存中的存储

接下来我们探讨数组在内存中的存储,看代码:
#include<stdio.h>
int main()
{
    int arr[]={1,2,3,4,5,6,7,8,9,10};
    int sz=sizeof(arr)/sizeof(arr[0]);
    int i=0;
    for(i=0;i<sz;i++)
    {
        printf("&arr[%d]=%p\n",i,&arr[i]);
    }
    return 0;
}
结论:数组在内存中是连续存放的。

二维数组的创建和初始化

二维数组的创建

//数组创建
Int arr[3][4];
Char arr[3][4];
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}};

二维数组的使用

二位数组的使用也是通过下标的方式。看代码:
#include<stdio.h>
int main()
{
    int arr[3][4]={{1,2,3},{4,5,6}};
    int i=0;
    for(i=0;i<3;i++)
    {
        int j=0;
        for(j=0;j<4;j++)
        {
            printf("%d ",arr[i][j]);
        }
        printf("\n");
    }
    /*char ch[5][6];
    int arr[]={1,2,3,4};
    int arr[][4]={{1,2,3,4},{5,6,7,8}};*/
    return 0;
}

二维数组在内存中的存储

像一维数组一样,这里我们尝试打印二位数组的每个元素
#include<stdio.h>
int main()
{
         int arr[3][4]={{1,2,3},{4,5,6}};
         int i=0;
         for(i=0;i<3;i++)
         {
                  int j=0;
                  for(j=0;j<4;j++)
                  {
                          printf("&arr[%d][%d]=%p\n",i,j,&arr[i][j]);
                  }
         }
         return 0;
}
结论:二位数组也是连续的

数组作为函数参数

往往我们在写代码的时候,会将数组作为参数传入函数,比如:我要实现一个冒泡排序(这里要讲思路)函数将一个整形数组排序,那我们将会这样使用该函数:
冒泡排序函数设计
//方法1:
#include<stdio.h>
void bubble_sort(int arr[],int sz)
{
         //确定冒泡排序的趟数
         int i=0;
         for(i=0;i<sz-1;i++)
         {
                  int flag=1;//假设这一趟要排序的数据已经有序
                  //每一趟冒泡排序
                  int j=0;
                  for(j=0;j<sz-1-i;j++)
                  {
                          if(arr[j]>arr[j+1])
                          {
                                   int tmp=arr[j];
                                   arr[j]=arr[j+1];
                                   arr[j+1]=tmp;
                                   flag=0;//本躺排序的数据其实不完全有序
                          }
                  }
                  if(flag==1)
                  {
                          break;
                  }
         }
}
int main()
{
         int arr[]={0,10,5,3,4,8,6,7,89,5,5};
         int i=0;
         int sz=sizeof(arr)/sizeof(arr[0]);
         //arr进行排序,排成升序
         //arr是数组,我们对数组arr进行传参,实际上传递过去的是数组arr首元素地址 &arr[0]
         bubble_sort(arr,sz);//冒泡排序函数
         for(i=0;i<sz;i++)
         {
                  printf("%d\n",arr[i]);
         }
         return 0;
}
数组名是什么?
#include<stdio.h>
int main()
{
    int arr[]={1,2,3,4,5,6,7};
    printf("%p\n",arr);
    printf("%p\n",arr+1);
    printf("%p\n",&arr[0]);
    printf("%p\n",&arr[0]+1);
    printf("%d\n",&arr);
    printf("%d\n",&arr+1);
    //int sz=sizeof(arr)/sizeof(arr[0]);
    //1.sizeof(数组名)-数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单????????
    //2.&数组名,数组名代表整个数组,&数组名,取出的是整个数组的地址
    /*int arr[10]={0};
    printf("%d\n",sizeof(arr));*/
    return 0;
}
结论:数组名是数组首元素的地址。(有两种情况例外)
1.sizeof(数组名)-数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位??????
2.&数组名,数组名代表整个数组,&数组名,取出的是整个数组的地址
如果数组名是首元素地址,那么:
int arr[10]={0};
printf("%d\n",sizeof(arr));
为什么结果是40?

操作符

分类:

算术操作符:

+  -  *  /  %
  1. 除了%操作符之外,其他的几个操作符可以作用于整数和浮点型。
  2. 对于/操作符如果两个操作数都为整数,执行整数除法,而只是有浮点数执行的就是浮点数??????
  3. %操作符的两个操作数必须为整数,返回的是整除之后的余数。
                                                

移位操作符:

<< 左移操作符
>> 右移操作符
左移操作符 移位规则:左边抛弃,右边补0
右移操作符 移位规则: 1.算术右移 右边丢弃 左边补原符号位
  1. 逻辑右移 右边丢弃 左边补0
警告:对于移位操作符,不要移动负数位,这个是标准定义的,例如:
Int num=10;
Num>>-1;//error

位操作符:

&   //按位与
|   //按位或
^   //按位异或
注意:他们的操作数必须是整数
int main()
{
    //& -按二进制位与
//00000000000000000000000000000011
    //00000000000000000000000000000101
    //00000000000000000000000000000001
    int a=3;
    int b=5;
    //int c=a&b;
    //int c=a|b;
    int c=a^b;
    
    printf("%d\n",c);
    //| -按二进制位或
    //00000000000000000000000000000011
    //00000000000000000000000000000101
    //00000000000000000000000000000111 
    //^ -按二进制位异或
    //相同为0,相异为1
    //00000000000000000000000000000011
    //00000000000000000000000000000101
    //00000000000000000000000000000110
    return 0;
}
问题:不能创建临时变量(第三个变量),实现两个数的交换:
#include<stdio.h>
int main()
{
    int a=5;
    int b=3;
    int tmp=0;
    printf("before:a=%d b=%d \n",a,b);
    /*tmp=a;
    a=b;
    b=tmp;*/
    //加减法 -可能会退出
    /*a=a+b;
    b=a-b;
    a=a-b;*/
    //异或的方法
    a=a^b;
    b=a^b;
    a=a^b;
    printf("after:a=%d b=%d \n",a,b);
}
练习:编写代码实现:求一个整数在内存中的二进制中1的个数
参考代码:
#include<stdio.h>
int main()
{
         int num=0;
         int count=0;
         scanf("%d",&num);
         //32bit
         //num&1==1
         int i=0;
         for(i=0;i<32;i++)
         {
                  if((num>>i)&1)
                          count++;
         }
         //00000000000000000000000000000000
         //00000000000000000000000000000000
         //00000000000000000000000000000000
         //统计num中补码中有几个1
         /*while(num)
         {
                  if(num%2==1)
                          count++;
                  num=num/2;
         }*/
         printf("该数在内存中个数为1的数有:%d\n",count);
         return 0;
}

赋值操作符:

赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值,也就是你可以给自己重新赋值。
复合赋值法:+=  -=  *=  /=  %=  >>=  <<=  &=  |=  ^=
int main()
{
         int a=10;
         a=a+2;
         a+=2;//复合赋值法
         
         a=a>>1;
         a>>=1;
         a=a & 1;;
         a &=1;
         printf("%d\n",a);
         return 0;
}
单目操作符:
!      逻辑反操作 
-       负值 
+       正值 
&       取地址 
sizeof      操作数类型长度(以字节为单位)
~       对一个数的二级制按位取反 
--      前置、后置-- 
++      前置、后置++ 
*       间接访问操作符(解引用操作符)
(类型) 强制类型转换
int main()
{
    /*int a=-5;
    a = -a;
    printf("a=%d\n",a);*/
    //int a=10;
 //     int* p=&a; //取地址操作符
    //*p=20; //解引用操作符
    int a=10;
    char c='r';
    char* p=&c;
    int arr[10]={0};
    //sizeof计算的变量所占内存空间的大小
    printf("%d\n",sizeof(a));//4
    printf("%d\n",sizeof(int));//4
    printf("%d\n",sizeof(c));//1
    printf("%d\n",sizeof(char));//1
    printf("%d\n",sizeof(p));//4
    printf("%d\n",sizeof(char*));//4
    printf("%d\n",sizeof(arr));//40
    printf("%d\n",sizeof(int [10]));//40
    return 0;
}
#include <stdio.h>
void test1(int arr[])
    {printf("%d\n",sizeof(arr));}
void test2(char ch[])
    {printf("%d\n",sizeof(ch));}
int main()
{
    /*short s=0;
    int a=10;
    printf("%d\n",sizeof(s=a+5));
    printf("%d\n",s);*/
    //int a=0;
    ////~按(2进制)位取反
    ////00000000000000000000000000000000000
    ////11111111111111111111111111111111111  -补码
    ////11111111111111111111111111111111110  -反码
    ////10000000000000000000000000000000001  -原码
    //printf("%d\n",~a);
    
    /*int a=11;
    a=a | (1<<2);
    printf("%d\n",a);
    a=a & (~(1<<2));
    printf("%d\n",a);*/
    //00000000000000000000000000001011
    //00000000000000000000000000000100
    //1<<2
    //00000000000000000000000000001111
    //11111111111111111111111111111011
    //00000000000000000000000000000100
    //00000000000000000000000000001011
    //int a=10;
    ////printf("%d\n",++a);  //前置++ 先++,后使用
    //printf("%d\n",a++);  //后置++ 先使用,后++
    //int a=(int)3.14; //int a=int(3.14);error
    //printf("%d\n",a);
    
    int arr[10]={0};
    char ch[10]={0};
    printf("%d\n",sizeof(arr)); //40
    printf("%d\n",sizeof(ch));  //10
    test1(arr);  //4 传递首元素地址用指针接收
    test2(ch);  //4
    return 0;
}

关系运算符

 >   >=  <  <=  !=  ==

逻辑运算符

&&   逻辑与
||    逻辑或
#include <stdio.h>
int main()
{
         /*int a=0;
         int b=3;
         int c=a && b;
         int d=a || b;
         printf("%d\n",c);
         printf("%d\n",d);*/
         int i=0,a=1,b=2,c=3,d=4;
         //i=a++ && ++b && d++;
         i=a++ || ++b || d++;
                  printf("a=%d\n b=%d\n c=%d\n d=%d\n",a,b,c,d);
         return 0;
}

条件操作符

exp 1 ? exp2 : exp3
int main()
{
    int a=10;
    int b=20;
    int max=0;
    /*if(a>b)
    {
        max=a;
    }
    else
        max=b;*/
    max=(a>b?a:b);
    printf("%d\n",max);
    /*if(a>5)
        b=3;
    else
        b=-3;*/
    /*b=(a>5?3:-3);
    printf("%d\n",b);*/
    return 0;
}

逗号表达式

exp1,exp2,exp3,....expN
逗号表达式,就是用逗号隔开的多个表达式,逗号表达式,从左向右依次执行,整个逗号表达式的结果是最后一个表达式的结果。

下标应用、函数调用和结构成员

1.[]下标应用操作符
操作数:一个数组名+一个索引值
2.()函数调用操作符,接受一个或多个操作数;第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
int get_max(int x,int y)
{return x>y?x:y;}
int main()
{
          //int arr[10];//创建数组
          //arr[9]=10; //使用下标应用操作符
          ////[]的两个操作数是arr和9.
         int a=0;
         int b=20;
         //调用函数的时候()就是函数调用操作符
         int max=get_max(a,b);
         printf("max=%d\n",max);
         return 0;
}
3.访问一个结构体
     .结构体,成员名
     ->结构体指针->成员名
#include<stdio.h>
//学生
//int float
//创建一个结构体类型-struct Stu
struct Stu
{
    //成员变量
    char name[20];
    int age;
    char id[20];
};
int main()
{
    int a=10;
    //使用struct Stu这个类型创建了一个学生对象s1,并初始化
    struct Stu s1={"张三",20,"2020022645"};
    struct Stu* ps=&s1;
    //结构体指针->成员名
    printf("%s\n",ps->name);
    printf("%d\n",ps->age);
    /*printf("%s\n",(*ps).name);
    printf("%d\n",(*ps).age);
    printf("%s\n",(*ps).id);*/
    /*printf("%s\n",s1.name);
    printf("%d\n",s1.age);
    printf("%s\n",s1.id);*/
    //结构体变量.成员名
    return 0;
}

表达式求值

表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。

隐式类型转换

C的整型算数运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通类型,这种转换称为整型提升。
整型提升的意义:
表达式的整形运算在CPU的相应运算器件内执行,CPU内整形运算器(ALU)的操作数的字节???????????int????????????????????CPU?????????????????????
因此,即使有两个char类型的相加,在CPU执行时实际上也要先转换位CPU内整型操作数的标准长度。
通用CPU(general purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令),所以,表达式中各种长度小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
//实例
Char a,b,c;
...
a=b+c;
b和c的值被提升为普通整型,然后在执行加法运算。
加法运算完成之后,结果将被截断,然后在存储于a中。
如何进行整体提升呢?
整型提升是按照变量的数据类型的符号位来提升的
//负数的整型提升
Char c1=-1;
变量c1的二进制位(补位)中只有8个比特位;
11111111
因为char为有符号的char
所以整型提升的时候,高位补充符号位,即为1
提升之后的结果是:
例子:
int main()
{
         /*char a=0xb6;
         short b=0xb600;
         int c=0xb6000000;
         if(a==0xb6)
                  printf("a");
         if(a==0xb600)
                  printf("b");
         if(a==0xb6000000)
                  printf("c");*/
         char c=1;
         printf("%u\n",sizeof(c));//1
         printf("%u\n",sizeof(+c));//4
         printf("%u\n",sizeof(!c));//1
         return 0;
}
c只要参与表达式运算,就会发生整型提升,所以sizeof(+c)是4个字节。

算数转换

如果某个操作符的各操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
Long double
Double
Float
Unsiged long int
Long  int
Unsigned int
Int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另一个操作数的类型后执行运算。 警告:但是类型转换要合理,要不然会有一些潜在的问题。
操作符的属性
复杂表达式的求值有三个影响的因素。
  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序
两个相邻的操作符先执行那个?取决于他们的优先级。如果两者优先级相同,取决于他们的结合性,操作符优先级
一些问题表达式
//表达式的求值部分由部分操作符的优先级决定。
int main()
{
         //float f=3.14;
         //int num=f;//隐式转换,会有精度丢失
         /*int a=10l;
         int b=39;
         int c=b+a*9;*/
         /*int i=10;
         i=i-- - --i * (i=-3)*i++ + ++i;
         printf("i=%d\n",i);*/
         int i=1;
         int ret=(++i)+(++i)+(++i);
         printf("%d\n",ret);
         printf("%d\n",i);
         return 0;
}

指针

指针是什么?

在计算机科学中,指针(pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中的另一个地方的值,通过地址能找到所需的变量单元;可以说,地址指向该变量单元。因此,将地址形象化称为“指针”,意思是通过它能找到以它为地址的内存单元。
指针是个变量,存放内存单元的地址(编号)。
对应代码:
int main()
{
         int a=10; //在内存中开辟一块空间
         int *p=&a; //这里我们对变量a,取出它的地址,可以使用&操作符。
                                   //将a的地址存放在p变量中,p就是一个指针变量。
         return 0;
}
总结:指针就是变量,用来存放地址得变量。(存放在指针中的值都被当作成地址来处理)。
·一个小的单元到底有多大?(1个字节)
·如何编址?
经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的是产生一个电信号正电或负电(1/0)
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
...
11111111 11111111 11111111 11111111
这里就是2的32次方个地址。
每个地址标识一个字节,那么我们就可以给(2^32byte==2^32/1024kb==2^32/1024/1024MB==2^32/1024/1024/1024GB==4GB)4GB的空闲进行编址。
·在32位的机器上,地址是32个0或者 1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节,
·那如果在64位机器上,如果有64根地址线,那一个指针的大小就是8个字节,才能存放一个??????
总结:
·指针是用来存放地址的,地址是唯一标示一块地址空间的
·指针的大小在32位平台是4个字节,在64位平台是8个字节。

指针和指针类型

Int num=10;
P=&num;
要将&num(num的地址)保存到p中,p是一个指针变量
Char *pc=NULL;
Int *pi=NULL;
Short *ps=NULL;
Float *pf=NULL;
Doble *pd=NULL;
指针的定义方式是:type+*,其中char*类型存放的是char类型变量地址,short*类型的指针是为了存放short类型变量地址,int*类型指针是为了存放int类型变量的地址。
指针+-整数
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;
}
总结:指针的类型决定了指针向前或者向后走一步有多大(距离).
指针的解引用
int main()
{
         int n=0x11223344;
         char *pc=(char *)&n;
         int *pi=&n;
         *pc=0;
         *pi=0;
         return 0;
}
总结:指针的类型决定了,对指针解引用的时候有多大权限(能操作几个字符),比如:char* 的指针解引用只能访问一个字符,而Int*的指针解引用就能访问4个字节。
 
野指针
 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针成因
  1. 指针未初始化
int main()
{
    int *p;//局部变量指针未初始化,默认为随机
    *p=20;
    return 0;
}
  1. 指针越界访问
int main()
{
    int arr[10]={0};
    int *p=arr;
    int i=0;
    for(i=0;i<=12;i++)
    {
        *(p++)=i;
    }
    return 0;
}
  1. 指针指向的空间释放
这里放在动态内存开辟的时候讲解过
int* test()
{
    int a=10;
    return &a;
}
int main()
{
    int* p=test();
    printf("%d\n",*p);
    return 0;
}
如何规避野指针
  1. 指针初始化
  2. 小心指针越界
  3. 指针指向空间释放及放置NULL
  4. 指针使用之前检查有效性
    
int main()
{
         //int b=0;
         //int a=10;
         //int* pa=&a;//初始化
         //int* p=NULL;//NULL-用来初始化指针,给指针赋值
         int a=10;
         int *pa=&a;
         *pa=20;
         //
         pa=NULL;
         *pa=10;
         return 0;
}

指针运算

·指针+-整数
#include<stdio.h>
int main()
{
         int arr[10]={1,2,3,4,5,6,7,8,9,10};
         int i=0;
         int sz=sizeof(arr)/sizeof(arr[0]);
         int* p=&arr[9];
         /*for(i=0;i<sz;i++)
         {
                  printf("%d\n",*p);
                  p++;
         }*/
         for(i=0;i<5;i++)
         {
                  printf("%d\n",*p);
                  //p+=2;
                  p-=2;
         }
         return 0;
}
·指针-指针
#include<stdio.h>
int main()
{
         char ch[5]={0};
         int arr[10]={1,2,3,4,5,6,7,8,9,10};
         printf("%d\n",&arr[9]-&arr[0]);
         printf("%d\n",&arr[9]-&ch[0]);//error
         return 0;
}
#include<stdio.h>
int my_strlen(char* str)
{
         char* start=str;
         char* end=str;
         while(*end != '\0')
         {
                  end++;
         }
         return end-start;//
}
int main()
{
         //strlen 求字符串长度
         //递归 -模拟实现了strlen-计数方式为1,递归方式为2
         //
         char arr[]="bit";
         int len=my_strlen(arr);
         printf("%d\n",len);
         return 0;
}
·指针的关系运算
#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp=&values[N_VALUES];vp>=&values[0];VP--)
{
         *vp=0;
}
实际在绝大部夫的编译器上是可以顺利完成任务的,然而我们还是避免这样写,因为标准并不保证可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较
·指针和数组
#include<stdio.h>
int main()
{
         int arr[10]={1,2,3,4,5,6,7,8,9,0};     
         printf("%p\n",arr);//地址-首元素的地址
         printf("%p\n",arr+1);
         printf("%p\n",&arr[0]);
         printf("%p\n",&arr[0]+1);
         printf("%p\n",&arr);
         printf("%p\n",&arr+1);
         //1.&arr -&数组名-数组名不是首元素地址-数组名表示整个数组 -&数组名-取出的是整个数组的地址
         //00D5FA2C 0092F8C0
         //2.sizeof(arr)-sizeof(数组名)-数组名表示整个数组-sizeof(数组名)计算的是整个数组的大小,单位是字节
         
         return 0;
}
可见数组名和数组首元素的地址是一样的。
结论:数组名表示的是数组首元素的地址。
Int arr[10]={1,2,3,4,5,6,7,8,9,0};
Int *p=arr;//p存放的是数组首元素的地址
既然我们可以把数组名当成地址存放到一个指针中,我们使用指针访问一个就成为可能;
例如:
int main()
{
         int arr[10]={1,2,3,4,5,6,7,8,9,0};
         int *p=arr;//指针存放数组首元素地址
         int i=0;
         int sz=sizeof(arr)/sizeof(arr[0]);
         for(i=0;i<sz;i++)
         {
                  printf("&arr[%d]=%p <======>p+%d=%p\n",i,&arr[i],i,p+i);
         }
         return 0;
}
所以p+i其实计算的是数组arr下标为i的地址,
那我们就可以直接通过指针来访问数组。
int main()
{
         int arr[10]={1,2,3,4,5,6,7,8,9,0};
         int *p=arr;//指针存放数组首元素地址
         int i=0;
         int sz=sizeof(arr)/sizeof(arr[0]);
         for(i=0;i<sz;i++)
         {
                  printf("%d ",*(p+i));
         }
         return 0;
}

二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?这就是二级指针。
int main()
{
    int a=10;
    int* p=&a;
    int** pp=&p;
    int*** ppp=&pp;
    return 0;
}
对于二级指针运算有:
·*ppp通过对pp中的地址进行解引用,这样找到pp,*ppp其实访问的就是pp

指针数组

存放指针的数组;
数组有:整型数组、字符数组
int main()
{
    int a=10;
    int b=20;
    int c=30;
    int* pa=&a;
    int* pb=&b;
    int* pc=&c;
    //整型数组 - 存放整型
    //字符数组 - 存放字符
    //指针数组 - 存放指针
    //int arr[10];
    int* arr2[3]={&a,&b,&c}; //指针数组
    int i=0;
    for(i=0;i<3;i++)
    {
        printf("%d ",*arr2[i]);
    }
    return 0;
}

实用调试技巧

什么是bug?

在程序设计中的术语,是指在软件运行中因为程序本身有错误而造成的功能不正常、体验不佳、死机、数据丢失、非正常中断等现象。

调试是什么?

编好程序后,用各种手段进行查错和排错的过程。作为程序的正确性不仅仅表现在正常功能的完成上,更重要的是对意外情况的正确处理。从心理学的角度考虑,开发人员和调试人员不应该是同一个人。

调试的基本步骤

·发现程序错误的存在
·以隔离,消除等方式对错误进行定位
·确定错误产生的原因
·提出纠正错误的解决办法
·对程序予以改正,重新测试

Debug和Release的介绍

Debug通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员测试程序。
Release称为发布版本,它往往是进行各种优化,使得程序在代码大小和运行速度上都是最??????????????????????????

调试常用几个快捷键

F5  启动调试,经常用来直接调到下一个断点处。
F9  创建断点和取消断点的重要作用,可以在程序任意位置设置断点。这要就可以使程序在想要的位置随意停止执行,继而一步步执行下去。
F10  逐过程,通常用来处理一个过程,一个过程可以是一次函数的调用,或者是一条语句??
F11  逐语句,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入函数??????????????????????
CTRL + F5 开始执行不调试,如果你想让程序直接运行起来而不调试就可以直接使用。
调试的时候查看程序的当前信息
查看临时变量的值
Release
Debug
如何写出(易于调试)的代码
优秀的代码:
  1. 代码运行正常
  2. Bug很少
  3. 效率高
  4. 可维护性高
  5. 注释清晰
  6. 文档齐全
    常见的coding技巧
  1. 使用assert
  2. 尽量使用const
  3. 养成良好的编码风格
  4. 添加必要的注释
  5. 避免编码的陷阱
示范:
模拟实现库函数:strcpy
void my_strcpy(char* dest,char* src)
//{
//  while(*src !='\0')
//  {
//      *dest=*src;
//      src++;
//      dest++;
//  }
//  *dest=*src;
//}
#include<assert.h>
void my_strcpy(char* dest,char* src)
{
         assert(dest !=NULL);//断言
         assert(src !=NULL);//断言
         
         if(dest !=NULL && src !=NULL)
         {
                  while(*dest++=*src++)
                  {
                          ;
                  }
         }
         
}
int main()
{
         //字符串拷贝
         //strcpy
         char arr1[]="###########";
         char arr2[]="bit";
         my_strcpy(arr1,arr2);
         printf("%s\n",arr1);
}

编程常见的错误

常见的错误分类
编译型错误
直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定。相对来说简单
链接型错误
看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符号不存在或者拼写错误。
运行时错误
借助调试,逐步定位问题。最难搞。

结构体

结构体的声明

结构体的基础知识
结构是一些值的集合,这些值称为成员变量,结构的每个成员可以是不同类型的变量。
结构体声明
例如描述一个学生
typedef struct Stu
{
    //成员变量
    char name[20];
    short age;
    char tele[12];
    char sex[5];
}s1,s2,s3;
结构成员的类型
结构成员可以是标量、数组、指针,甚至是其他结构体。
结构体变量的定义和初始化
有了结构体类型,那如何定义变量,其实很简单。
#include<stdio.h>
/ typedef struct Stu
//{
//  //成员变量
//  char name[20];
//  short age;
//  char tele[12];
//  char sex[5];
//}Stu;
//int main()
//{
//  Stu s1={"张三",22,"15213346414","男"};//局部变量
//  struct Stu s2={"罗翔",22,"15113346414","男"};
//  return 0;
//}
结构体成员的访问
结构体变量访问成员 结构体变量的成员是通过操作符(.)访问的。点操作符接受两个操作数。例如:
#include<stdio.h>
struct S
{
    int a;
    char c;
    char arr[20];
    double d;
};
struct T
{
    char ch[10];
    struct S s;
    char *pc;
};
    
int main()
{
    char arr[]="hello wang\n";
    struct T t={"hehehe",{100,'w',"hello world",3.14},arr};
    printf("%s\n",t.ch);
    printf("%d\n",t.s.a);
    printf("%s\n",t.s.arr);
    printf("%lf\n",t.s.d);
    printf("%s\n",t.pc);
    return 0;
}
结构体传参
#include<stdio.h>
typedef struct Stu
{
    //成员变量
    char name[20];
    short age;
    char tele[12];
    char sex[5];
}Stu;
 void Print1(Stu tmp)
 {
     printf("name: %s\n",tmp.name);
     printf("age: %d\n",tmp.age);
     printf("tele: %s\n",tmp.tele);
     printf("sex: %s\n",tmp.sex);
    
 }
 void Print2(Stu* ps)
 {
     printf("name:%s\n",ps->name);
     printf("age:%d\n",ps->age);
     printf("tele:%s\n",ps->tele);
     printf("sex:%s\n",ps->sex);
 }
int main()
{
    Stu s1={"张三",22,"15213346414","男"};//局部变量
    struct Stu s2={"罗翔",22,"15113346414","男"};
    Print1(s1);
    Print2(&s2);
    return 0;
}
函数传参的时候,参数是需要压栈的。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。
结论:结构体传参的时候,要传结构体的地址
数据在内存中的存储
数据类型介绍
Char     //字符数据类型
Short     //短整型
Int         //整型
Long    //长整型
Long long   //更长的整型
Float   //单精度浮点数
Double  //双精度浮点数
C语言类型
  1. 内置类型
Char    short    int     long        float   double
  1. 自定义类型(构造类型)
整型家族:
Char
Unsigned char
Signed char
Short
Usigned short[int]
Signed short [int]
构造类型
数组类型
结构体类型struct
枚举类型 enum
联合类型 union
    指针类型
Int *pi;
Char *pc;
Float* pf;
Void* pv;
空类型:
Void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型。
整型在内存中的存储
原码、反码、补码
计算机中有符号数(整形)有三种表示方法,即原码、反码、补码
三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位三种表示方法各不相同。
整数
  1. 有符号数(正数:原码、反码、补码相同 负数:原码、反码、补码不同,要进行计算)
  2. 无符号数(原码、反码、补码相同)
原码
直接将二级制按照正负数的形式翻译成二级制就可以。
反码
将原码的符号位不变,其他位依次按位去反就可以得到了。
补码
反码+1就可以得到补码。
正数的原、反、补码都相同
对于整形来说:数据存放内存中其实存放的是补码。
为什么呢?
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理,同时,加减法也可以统一处理(cpu只有加减法)此外,补码与原码相互转??????????????????????????????????????????????
什么是大端小端
大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中。 
小端(存储)模式,是指数据的地位保存在内存的低地址中,而数据的高位,保存在内存的高地址中。
大端字节序存储模式
小端字节序存储模式
为什么有大端和小端
这是因为在计算机系统中,我们是以字字节为单位的,每个地址单元都对应看一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位32位的处理器,由于存储器宽度大于一个字节,那么必然存在着一个如果将多字节安排的问题,因此就导致大端????????????????
例如一个16bit的short型x,在内存中的地址为0X0010,x的值为0X1122,那么0X11为高字节,0X22为低字节。对于大端模式,就将0X11放在低地址中,即0X0010中,0X22 放在高地址中,即0X0011中,小端模式刚好相反。我们常用的X86是小端,而KEIL CS1则为大端模式,有些ARM处理器还可以有硬件来选择大端还是小端。
百度2015年系统工程师笔试题
请简述大端字节和小端字节的概念,设计一个小程序判断当前机器的字节序。
#include<stdio.h>
//int main()
//{
//  int a=20;
//  //00000000000000000000000000010100 原码
//  //00000000000000000000000000010100 反码
//  //0000 0000 0000 0000 0000 0000 0001 0100 补码
//  //0X00000014
//  int b=-10;
//  //10000000000000000000000000001010 原码
//  //11111111111111111111111111110101 反码
//  //1111 1111 1111 1111 1111 1111 1111 0110 补码
//  //0XFFFFFFF6
//  return 0;
//}
//int main()
//{
//  1-1;
//  //1+(-1)
//  //00000000000000000000000000000001
//  //11111111111111111111111111111111
//  //00000000000000000000000000000000
//
//  //10000000000000000000000000000001
//  //11111111111111111111111111111110
//  //11111111111111111111111111111111
//}
//
//int main()
//{
    //写一段代码告诉我们当前机器的字节序是什么
    //返回1,小端;返回0,大端
    /* int a=1;
     char* p=(char*)&a;
     if(*p==1)
     {
          printf("小端\n");
     }
     else
     {
         printf("大端\n");
     }*/
//  int check_sys();
//  int ret=check_sys();
//  if(ret==1)
//   {
//        printf("小端\n");
//
//   }
//   else
//   {
//       printf("大端\n");
//   }
//  return 0;
//}
//int check_sys()
//{
//  int a=1;
//  char *p=(char*)&a;
//  if(*p==1)
//      return 1;
//  else
//      return 0;
//}
//int check_sys()
//{
//  int a=1;
//  return *(char*)&a;
//}
//指针类型的意义
//1、指针类型决定了指针解引用操作符能访问几个字节 char*p;*p访问了1个字节,int*p;*p访问4个字节
//2、指针类型决定了指针+1,-1,加的或者减的是几个字节:char *p;p+1,跳过一个字符?int *p;p+1,????????????-4???????
int main()
{
    int a=0X11223344;
    /*int* p=&a;
    *p=0;*/
    char* p=&a;
    *p=0;
    return 0;
}
Char  有符号的char的范围是-128—127 无符号范围0—255
Signed char
Unsigned char
//int main()
//{
//     char a=-1;
//     //100000000000000000000000000000001
//     //111111111111111111111111111111110
//     //111111111111111111111111111111111
//     //11111111
//     signed char b=-1;
//     //11111111
//     unsigned char c=-1;
//     //11111111
//     printf("a=%d,b=%d,c=%d",a,b,c);
//     //-1 -1 255
//     return 0;
//}
int main()
{
         char a=128;
         //10000000000000000000000001000000
         //11111111111111111111111110111111
         //11111111111111111111111111000000
         //10000000
         printf("%u\n",a);
         return 0;
}
//int main()
//{
//     int i=-20;
//     unsigned int j=10;
//     //10000000 00000000 00000000 00010100
//     //11111111 11111111 11111111 11101011
//     //11111111 11111111 11111111 11101100
//     //00000000 00000000 00000000 00001010 10
//     //11111111 11111111 11111111 11110110
//     //11111111 11111111 11111111 11110101
//     //10000000 00000000 00000000 00001010
//     printf("%d\n",i+j);
//     return 0;
//}
#include<windows.h>
//int main()
//{
//     unsigned int i;
//     for(i=9;i>=0;i--)
//     {
//              printf("%u\n",i);
//              Sleep(100);
//     }
//     return 0;
//}
//int main()
//{
//     char a[1000];
//     int i;
//     for(i=0;i<1000;i++)
//     {
//              a[i]=-1-i;
//     }
//     printf("%d" ,strlen(a));
//     return 0;
//}
unsigned char i=0; //0-255
int main()
{
         for(i=0;i<=255;i++)
         {
                  printf("hello world\n");
         }
return 0;
}
浮点数在内存中的存储
常见的浮点数:3.1415926    1E10
浮点数家族包括:float、double、long double类型。
浮点数表示范围:float.h中定义
浮点数存储的例子:
int main()
{
         int n=9;
         float *pFloat=(float *)&n;
         printf("n的值为:%d\n",n);
         printf("*pFloat的值为:%f\n",*pFloat);
         *pFloat=9.0;
         printf("num的值为:%d\n",n);
         printf("*pFloat的值为:%f\n",*pFloat);
         return 0;
}
根据国际标准IEEE(电气电子工程协会)754,任意一个二进制数v可以表示为:
·(-1)^S*M*2^E
·(-1)^s表示符号位,当s=0,v为正数;当s=1,v为负数
·M表示有效数字,大于等于1,小于2.
·2^E表示指数位
举例来说:十进制的5.0,写成二进制是101.0,相当于1.01*2^2,那么按照上面的v格式,可以得出s=0,M=1.01,E=2。
IEEE 754规定:对于32位的浮点数,最高的1位是符号位s,接着是8位是指数E,剩下的23位为有效数字M。
对于64位的浮点数,最高位的1位是符号位s,接着的11位是指数E,剩下的52位位有效数字M??
IEEE 754对有效数字M和指数E,还有一些特别规定。前面说过,1<=M<2,也就是说,M可以???1.XXXXXXXXXX????????????XXXXXXXXXX???????????????
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保留后面的XXXXXXX部分,
至于指数E,情况就比较复杂。
首先,E为一个无符号整数(unsigned int),这就意味着,如果E为8位数,它的取值范围0-255;如果E为11位,它的取值范围为0-2047,但是,我们知道,科学计数法中的E是可以出现复数的,所以IEEE754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,之歌中间数是127;对于11位的E,这个中间数是1023,比如,2^10的E是10,所以保存成32???????????????????????10+127=137????10001001???
E不全位0或不全为1