C语言-基础篇

0 前言

>>返回AUTOSAR系列文章目录<<

1 第一个C程序

#include <stdio.h>
 
int max(int num1, int num2);   // 函数声明
 
int main ()
{
   /* 把两个数字保存到计算机中 */
   int a = 100;
   int b = 200;
   int ret;      // ret存放结果
 
   /* 调用函数来获取最大值 */
   ret = max(a, b);
   printf( "Max value is : %d\n", ret );
   
   return 0;
}
 
/* 函数返回两个数中较大的那个数 */
int max(int num1, int num2) 
{
   /* 局部变量声明 */
   int result;
 
   if (num1 > num2)
      result = num1;
   else
      result = num2; 
      
   return result; 
}

1.1 C语言规则

在C语言中不遵守编译器的规定,编译器在编译时就会报错,这个规定叫作规则。

  1. 预处理
#include <stdio.h>

#include 是一个预处理命令,用来引入头文件。printf() 函数在 stdio.h 头文件中声明。stdio.h 是一个头文件 (standard I/O) 。当编译器遇到 printf() 函数时,如果没有找到 stdio.h 头文件,会发生编译错误。

  1. 注释
// 函数声明
/* 把两个数字保存到计算机中 */

//用于单行注释,/* ... */ 用于多行注释。

  1. 声明
int max(int num1, int num2);

除主函数外,其他函数需要先声明后调用。

  1. 分号

在 C 程序中,分号是语句结束符。也就是说,每个语句必须以分号结束。它表明一个逻辑实体的结束。

  1. 主程序
int main ()
{
    // code
    return 0}

所有的 C 语言程序都需要包含 main()函数。 代码从main() 函数开始执行。
现在 C 标准里规定 main() 返回值必须为int,所以必须写成是int main()return语句用于表示退出程序。main()的返回值是 int 类型,所以是return 0

1.2 C语言规范

规范是人为的、约定成俗的,即使不按照那种规定也不会出错。
当企业或者项目有明确规范时,以企业或项目规范为准。

  1. 空行

两个相对独立的程序块、变量说明之后必须要加空行。空行起着分隔程序段落的作用。空行得体将使程序的布局更加清晰。空行不会浪费内存。

定义变量后要空行。尽可能在定义变量的同时初始化该变量,即遵循就近原则。

每个函数定义结束之后都要加空行。

  1. 空格

关键字之后要留空格。像 constcase 等关键字之后至少要留一个空格,否则无法辨析关键字。像 ifforwhile 等关键字之后应留一个空格再跟左括号(,以突出关键字。

函数名之后不要留空格,应紧跟左括号(,以与关键字区别。

(向后紧跟;),;这三个向前紧跟;紧跟处不留空格。

,之后要留空格。如果;不是一行的结束符号,其后要留空格。

双目运算符的前后应当加空格。如 ===!=+=-=*=/=%=>>=<<=&=^=|=><=>>=+*/%&|&&||<<>>^

单目运算符!~++---(负号)*(指针)&(地址) 等前后不加空格。

像数组符号[]、结构体成员运算符.、指向结构体成员运算符->,这类操作符前后不加空格。

对于表达式比较长的for 语句和if语句,为了紧凑起见,双目运算符可以适当地去掉一些空格。但 forif 后面紧跟的空格不可以删,其后面的语句可以根据语句的长度适当地去掉一些空格。例如 for (i=0; i<10; i++)

  1. 对齐

{}分别都要独占一行。互为一对的{}要位于同一列,并且与引用它们的语句左对齐。

{}之内的代码要向内缩进一个Tab,且同一地位的要左对齐,地位不同的继续缩进。

  1. 代码行

一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且便于写注释。

ifelseforwhiledo 等语句自占一行,执行语句不得紧跟其后。此外,非常重要的一点是,不论执行语句有多少行,就算只有一行也要加{},并且遵循对齐的原则,这样可以防止书写失误。

  1. 注释

当代码比较长,特别是有多重嵌套的时候,应当在段落的结束处加注释,这样便于阅读。

每一条宏定义的右边必须要有注释,说明其作用。


2 基本数据类型

2.1 位、字节与存储单元

计算机内存中的小存储单位为位(bit),每个位存储一个01

计算机对内存管理的最小单位为字节(Byte),每字节为8位,计算机给每个字节分配一个十六进制表示的地址。

/***** 示例程序 *****/
#include<stdio.h>
int main()
{
    int a = 10;
    printf("a的地址为:%p", &a);
    return 0;
}


/***** 示例结果 *****/
a的地址为:0x7ffc036a3fbc

计算机中一个数据存放占用的空间叫做存储单元,存储单元都是字节的整数倍。不同的数据会占用多个相邻的字节。编程中使用sizeof来计算数据类型的存储单元大小。

/***** 示例程序 *****/
#include<stdio.h>
int main()
{
    int a;
    printf("int型数据的字节数为:%d", sizeof(a));
    return 0;
}


/***** 示例结果 *****/
int型数据的字节数为:4

各类型数据的存储单元如下:

类型32位平台64位平台
(unsigned/signed) char1字节1字节
(unsigned/signed) short2字节2字节
(unsigned/signed) int4字节4字节
(unsigned/signed) long4字节8字节
(unsigned/signed) float4字节4字节
(unsigned/signed) double8字节8字节
(unsigned/signed) long long8字节8字节
void4字节8字节
指针4字节8字节

2.2 整数类型 int short long

可以先声明,也可以声明的同时赋值。
C语言支持一行声明或赋值多个变量,但不建议使用。

int earn;
short hogs = 21;

2.2.1 关键字 typedef

C 语言提供了typedef关键字,您可以使用它来为数据类型取一个新的名字。

typedef关键字常用来重新定义整数常量名称,使得整数类型的内存大小更直观。

typedef unsigned char     uint8;
typedef short int         int16;
typedef int               int32;

#if __WORDSIZE == 64
typedef long int          int64;
#else
__extension__
typedef long long int     int64;
#endif

2.2.2 整数值范围

整数类型范围
uint80 到 255
int8-128 到 127
uint160 到 65,535
int16-32,768 到 32,767
uint320 到 4,294,967,295
int32-2,147,483,648 到 2,147,483,647

2.2.3 整数值格式

  1. 整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数:0x0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制。

  2. 整数常量也可以带一个后缀,后缀是 UL的组合,U表示无符号整数(unsigned),L 表示长整数(long)。后缀可以是大写,也可以是小写,UL 的顺序任意。

85         /* 十进制 */
0213       /* 八进制 */
0x4b       /* 十六进制 */
30         /* 整数 */
30u        /* 无符号整数 */
30l        /* 长整数 */
30ul       /* 无符号长整数 */

2.2.4 整数溢出

/***** 示例程序 *****/
#include<stdio>
int main()
{
    int i =2147483647
    unsigned int j = 4294967295;
    
    printf("%d %d %d\n", i, i+1, i+2);
    printf("%u %u %u\n", j, j+1, j+2);
    return 0;
}


/***** 示例结果 *****/
2147483647 -2147483648 -2147483647
4294967295 0 1

整数变量为变量类型最大值+1时,变量值变为变量类型最小值,其他值依次类推。系统并不通知用户。最大值+1变为最小值,最小值-1变为最大值。

2.3 字符类型 char

char类型存储字符,从技术上来看,char类型实际上存储的是整数而不是字符,所以char可以被typedefuint8
可以先声明,也可以声明的同时赋值。注意区分字符用‘ ’,字符串用“ ”。单字符的字符串比字符多一位'\0'

char grade;        //正确
char grade = 'A';  //正确
char grade = 65;   //正确
char grade = A;    //错误,A是变量
char grade = "A";  //错误,“A”是字符串

2.3.1 转义字符

字符常量是括在单引号中,例如,'x'可以存储在char 类型的简单变量中。

字符常量可以是一个普通的字符(例如'x')、一个转义序列,(例如 '\t'),或一个通用的字符(例如 '\u02C0')。

在 C 中,有一些特定的字符,当它们前面有反斜杠时,它们就具有特殊的含义,被用来表示如换行符\n或制表符\t等。

\\         \ 字符
\'         ' 字符
\"         " 字符
\?         ? 字符
\a         警报铃声
\b         退格键
\f         换页符
\n         换行符
\r         回车
\t         水平制表符
\v         垂直制表符

2.4 浮点类型 float double

浮点常量由整数部分、小数点、小数部分和指数部分组成。您可以使用小数形式或者指数形式来表示浮点常量。
当使用小数形式表示时,必须包含整数部分、小数部分,或同时包含两者。当使用指数形式表示时, 必须包含小数点、指数,或同时包含两者。带符号的指数是用eE 引入的。

3.14159       /* 合法的 */
314159E-5L    /* 合法的 */
.2            /* 合法的 */
100.          /* 合法的 */

3 复合数据类型

3.1 数组

数组用[ ]选择下标,用{ }输入元素。

#include <stdio.h>
int main ()
{
   int n[10]; /* n 是一个包含 10 个整数的数组 */
   int i,j;
   
   /* 初始化数组元素 */         
   for ( i = 0; i < 10; i++ )
   {
      n[i] = i + 100; /* 设置元素 i 为 i + 100 */
   }   
   
   /* 输出数组中每个元素的值 */
   for (j = 0; j < 10; j++ )
   {
      printf("Element[%d] = %d\n", j, n[j] );
   } 
   
   return 0;
}

数组可以是任意基本数据类型

int nannies[22];
char actors[26];
long big[500];

注意,char类型的数组最后一位为\0才成为字符串。

声明的同时定义数组。可以省略数组长度。

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

3.1.1 多维数组

float rain[2][5] =
{
    {1, 2, 3, 4, 5},
    {6, 7, 8, 9, 10}
};

该数组理解为数组rain有2个元素,每个元素为5位数组。数组的存储顺序为:
rain[0][0], rain[0][1], rain[0][2], rain[0][3], rain[0][4], rain[1][0], rain[1][1], rain[1][2], rain[1][3], rain[1][4]

3.2 字符串

在 C 语言中,字符串实际上是使用'\0'终止的一维字符数组。因此,一个以'\0' 结尾的字符串,包含了组成字符串的字符。

  • ‘\0’字符与NULL字符
    ‘\0’是ASCII码定义的0号字符,即空字符,用于表示字符串结束。
    NULL是宏#define NULL (void*) 0定义的指向不存在地址的指针,用于空指针。

下面的声明和初始化创建了一个 “Hello” 字符串。由于在数组的末尾存储了空字符,所以字符数组的大小比单词“Hello”的字符数多一个。

char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

依据数组初始化规则,您可以把上面的语句写成以下语句:

char greeting[] = "Hello";

您可以使用空格做分隔符,把一个很长的字符串常量进行分行。
下面的实例显示了一些字符串常量。下面这三种形式所显示的字符串是相同的。

"hello, dear"
"hello, \
dear"
"hello, " "d" "ear"

3.2.1 操作字符串的函数

# include  <string>
函数说明
strcpy(s1, s2);复制字符串 s2 到字符串 s1。
strcat(s1, s2);连接字符串 s2 到字符串 s1 的末尾。
strlen(s1);返回字符串 s1 的长度。
strcmp(s1, s2);如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
strchr(s1, ch);返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
strstr(s1, s2);返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。

3.3 枚举 enum

  1. 枚举允许一次性#define多个int型常量

  2. 枚举默认第一个值是0,后面一个值是前一个值+1

  3. 可以将其中一个枚举量=整数值,后面的值依次+1

/***** 示例程序 *****/
#include <stdio.h>
enum day_type
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};
 
int main()
{
    enum day_type day;
    
    day = WED;
    printf("%d",day);
    return 0;
}


/***** 示例结果 *****/
3

3.4 结构体 struct

  1. 结构体作为函数参数
#include <stdio.h>
#include <string.h>
 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
 
/* 函数声明 */
void printBook( struct Books book );
int main( )
{
   struct Books Book1;        /* 声明 Book1,类型为 Books */
   struct Books Book2;        /* 声明 Book2,类型为 Books */
 
   /* Book1 详述 */
   strcpy( Book1.title, "C Programming");
   strcpy( Book1.author, "Nuha Ali"); 
   strcpy( Book1.subject, "C Programming Tutorial");
   Book1.book_id = 6495407;
 
   /* Book2 详述 */
   strcpy( Book2.title, "Telecom Billing");
   strcpy( Book2.author, "Zara Ali");
   strcpy( Book2.subject, "Telecom Billing Tutorial");
   Book2.book_id = 6495700;
 
   /* 输出 Book1 信息 */
   printBook( Book1 );
 
   /* 输出 Book2 信息 */
   printBook( Book2 );
 
   return 0;
}
void printBook( struct Books book )
{
   printf( "Book title : %s\n", book.title);
   printf( "Book author : %s\n", book.author);
   printf( "Book subject : %s\n", book.subject);
   printf( "Book book_id : %d\n", book.book_id);
}
  1. 指向结构体的指针

可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:

struct Books *struct_pointer;

现在,您可以在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,请把 & 运算符放在结构名称的前面,如下所示:

struct_pointer = &Book1;

为了使用指向该结构的指针访问结构的成员,您必须使用-> 运算符,如下所示:

struct_pointer->title;
#include <stdio.h>
#include <string.h>
 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
 
/* 函数声明 */
void printBook( struct Books *book );

int main( )
{
   struct Books Book1;        /* 声明 Book1,类型为 Books */
   struct Books Book2;        /* 声明 Book2,类型为 Books */
 
   /* Book1 详述 */
   strcpy( Book1.title, "C Programming");
   strcpy( Book1.author, "Nuha Ali"); 
   strcpy( Book1.subject, "C Programming Tutorial");
   Book1.book_id = 6495407;
 
   /* Book2 详述 */
   strcpy( Book2.title, "Telecom Billing");
   strcpy( Book2.author, "Zara Ali");
   strcpy( Book2.subject, "Telecom Billing Tutorial");
   Book2.book_id = 6495700;
 
   /* 通过传 Book1 的地址来输出 Book1 信息 */
   printBook( &Book1 );
 
   /* 通过传 Book2 的地址来输出 Book2 信息 */
   printBook( &Book2 );
 
   return 0;
}

void printBook( struct Books *book )
{
   printf( "Book title : %s\n", book->title);
   printf( "Book author : %s\n", book->author);
   printf( "Book subject : %s\n", book->subject);
   printf( "Book book_id : %d\n", book->book_id);
}

3.5 位域 struct

有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有01 两种状态,用 1 位二进位即可。为了节省存储空间,并使处理简便,C 语言又提供了一种数据结构,称为"位域"或"位段"。

struct k{
    int a:1;
    int  :2;    /* 该 2 位不能使用 */
    int b:3;
    int c:2;
};
  1. 由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位

  2. 位域可以是无名位域,这时它只用来作填充或调整位置。无名的位域是不能使用的

main()
{
    struct bs{
        unsigned a:1;
        unsigned b:3;
        unsigned c:4;
    } bit,*pbit;
    bit.a=1;    /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
    bit.b=7;    /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
    bit.c=15;    /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
    
    printf("%d,%d,%d\n",bit.a,bit.b,bit.c);    /* 以整型量格式输出三个域的内容 */
    pbit=&bit;    /* 把位域变量 bit 的地址送给指针变量 pbit */
    pbit->a=0;    /* 用指针方式给位域 a 重新赋值,赋为 0 */
    pbit->b&=3;    /* 使用了复合的位运算符 "&=",相当于:pbit->b=pbit->b&3,位域 b 中原有值为 7,
                   与 3 作按位与运算的结果为 3(111&011=011,十进制值为 3) */
    pbit->c|=1;    /* 使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 15 */
    printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c);    /* 用指针方式输出了这三个域的值 */
}

4 运算符

4.1 算数运算符

算数运算符说明
+
-
*
/
%取模运算符,整除后的余数
++自增
--自减
  1. a++++a的区别
#include <stdio.h> 
int main()
{
   int c;
   int a = 10;
   c = a++;     //先赋值后运算,a=11,c=10

   a = 10;
   c = a--;     //先赋值后运算,a=9,c=10
   
   a = 10;
   c = ++a;     //先运算后赋值,a=11,c=11

   a = 10;
   c = --a;     //先运算后赋值,a=9,c=9
}
  1. 除法运算符/注意数据类型,整数相除得到整数,结果的小数部分被抛弃。
#include <stdio.h> 
int main()
{
    int a;
    float b;
    
    a = 5/4;       // a = 1
    b = 5.0/4;     // b = 1.250000
    return 0;
}

4.2 关系运算符

关系运算符说明
==等于
!=不等于
>大于
<小于
>=大于或等于
<=小于或等于

4.3 逻辑运算符

逻辑运算符说明
&&逻辑与
||逻辑或
!逻辑非

4.4 位运算符

位运算符说明
&位与
|位或
~位取反
^位异或
<<位左移
>>位右移

4.5 赋值运算符

赋值运算符说明
=
+=
-=
*=
/=
%=

5 基本语法

5.1 关键字 break 和 continue

关键字说明
break退出所在的for, switch循环体
continue结束当前循环迭代,进入下一步循环迭代

5.2 if 语句

if (表达式1)
    语句1;
else if (表达式2)
    语句2;
else
    语句3;

5.3 switch 语句

switch(表达式)
{
    case 常量表达式1:语句1;
                    break;
    case 常量表达式2:语句2;
                    break;
    ...
    default:语句n+1;
            break;
}

注意switch语句中一个case满足后,程序依然会判断后面的case是否满足,所以需要配合break语句。

5.4 三元运算符

条件 ? 条件真时执行语句 : 条件假时执行语句

5.5 for 语句

#include<stdio.h>
#include<math.h>

int main()
{
    int i,j;
    
    printf("100以内的素数有:\n");
    
    for(i=2; i<100; i++)
    {
        for(j=2; j<sqrt(i); j++)
        {
            if(i%j == 0)
            {
            break; 
            }
        }
        if(j > sqrt(i))
        {
        printf("%d,\t", i);
        }
    }
}

5.6 while 语句

#include  <stdio.h>

int main()
{
    int  sum=0;
    int  num=1;
    int  sum2=0;
    int  num2=2;
    
    while (num < 100)
    {
        sum=sum+num;
        num=num+2;
    }
    printf("奇数和为:%d\n",sum);

    while (num2 <= 100)
    {
        sum2=sum2+num2;
        num2=num2+2;
    }
    printf("偶数和为:%d\n",sum2);
    return 0}

6 指针

指针(pointer)是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:

int*    ip;    /* 一个整型的指针 */
double* dp;    /* 一个 double 型的指针 */
float*  fp;    /* 一个浮点型的指针 */
char*   ch;     /* 一个字符型的指针 */

所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的int型十六进制数。

但是指针声明时,必须声明成指针所指向的变量或常量的数据类型。指针如果指向一个float型数据,则指针必须被声明为 float*。这是因为不同数据类型的存储单元字节数不同,比如floae型数据存储单元为4字节,意味着从float的指针开始,连续读取4个字节才是完整的float数据。

6.1 指针运算符

地址运算符 &

ptr = &val;     // 把val的地址赋给ptr

所谓地址,是数据存储单元的第一个字节的字节地址。

间接运算符*

val = *ptr;    // 找到ptr所存的地址,把内容赋值给val

在声明指针变量的时候,*ip=指针地址,如果没有确切的地址可以赋值,为指针变量赋一个null值是一个良好的编程习惯。
在使用指针变量的时候,*ip = 实际变量值,ip = 指针地址。

/***** 示例程序 *****/
#include <stdio.h>
 
int main ()
{
   int* ptr = NULL;
   
   printf("ptr 的地址是 %p\n", ptr  );
   
   return 0;
}


/***** 示例结果 *****/
ptr 的地址是 0x0
/***** 示例程序 *****/
#include <stdio.h>
int main ()
{
   int  var = 20;   /* 实际变量的声明 */
   int* ip;        /* 指针变量的声明 */
 
   ip = &var;  /* 在指针变量中存储 var 的地址 */
 
   printf("var的地址: %p\n", &var  ); 
   /* 在指针变量中存储的地址 */
   printf("ip存储的地址: %p\n", ip ); 
   /* 使用指针访问值 */
   printf("*ip 的值: %d\n", *ip ); 
   
   return 0;
}


/***** 示例结果 *****/
var的地址: bffd8b3c
i存储的地址: bffd8b3c
*ip 的值: 20

6.2 指针与数组

数组名是数组首元素的地址,所以下面语句成立:

int arr[10];
arr == &arr[0];

指针+1指的是指向下一个存储单元地址:

/***** 示例程序 *****/
#include<stdio.h>
int main()
{
    int arr[10];
    printf("%p  %p", arr, arr+1);
    return 0;
}


/***** 示例结果 *****/
0x7fffb2908ab0 , 0x7fffb2908ab4

arrarr+1差值为4,因为int数据存储单元占4个字节。arr+1实际指的是arr指针移动一个存储单元。

arr是数组元素首地址,arr+index是元素arr[index]的地址,而*(arr+index)arr[index]的值。

6.3 指针与函数

函数处理数组时,需要输入数组地址和数组元素个数。

#include<stdio>
#define SIZE 10

int sum(int arr[], int n);

int main()
{
    int numble[SIZE] = {20, 10, 5, 39, 4, 16, 19, 26, 31, 20};
    long answer;
    
    answer = sum(numble, SIZE);
    return 0;
}

int sum(int arr[], int n)
{
    int i;
    int total;
    for (i=0; i<n; i++)
    {
        total += arr[i];
    }
    return total;
}

以下两种函数定义方式等价,都是传入指针:

int sum(int arr[], int n);
int sum(int* arr, int n);

>>返回AUTOSAR系列文章目录<<

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值