c/c++0-语言基础

0,参考链接:

C语言中文网

C library:提供了所有C语言标准函数的原型,并给出了详细的介绍和使用示例,可以作为一部权威的参考手册。

C++ STL标准手册

1,基本数据类型,输入输出,运算符

基本数据类型

字符型 短整型 整型 长整型 单精度浮点型 双精度浮点型
数据类型 char short int long float double
长度 1 2 4 4 4 8

(1)获取某个数据类型的长度可以使用 sizeof 操作符
(2)不希望设置符号位,可以在数据类型前面加上 unsigned 关键字
(3)字符串定义/数组和指针: char str1[] = "abc"; char *str2 = "abc";这两种形式是有区别的,第一种形式的字符串所在的内存既有读取权限又有写入权限,第二种形式的字符串所在的内存只有读取权限,没有写入权限。printf()、puts() 等字符串输出函数只要求字符串有读取权限,而 scanf()、gets() 等字符串输入函数要求字符串有写入权限,所以,第一种形式的字符串既可以用于输出函数又可以用于输入函数,而第二种形式的字符串只能用于输出函数。
(4)当除数和被除数都是整数时,运算结果也是整数;如果不能整除,那么就直接丢掉小数部分,只保留整数部分;一旦除数和被除数中有一个是小数,那么运算结果也是小数,并且是 double 类型的小数。
(5)C语言中的取余运算只能针对整数,也就是说%的两边都必须是整数;余数可以是正数也可以是负数,由 % 左边的整数决定:如果 % 左边是正数,那么余数也是正数;如果 % 左边是负数,那么余数也是负数。
(6)++ 在变量前面和后面是有区别的:++ 在前面叫做前自增(例如 ++a)。前自增先进行自增运算,再进行其他操作。++ 在后面叫做后自增(例如 a++)。后自增先进行其他操作,再进行自增运算。自减(--)也一样,有前自减和后自减之分。
(7)强制类型转换的格式为: (type_name) expression

输入

gets():获取一行数据,并作为字符串处理。gets() 能读取含有空格的字符串,而 scanf()不能(其实用上正则表达式也可以)。
getchar()、getche()、getch():这三个函数都用于输入单个字符。

函数 缓冲区 头文件 回显 适用平台
getchar() stdio.h Windows、Linux、Mac OS 等所有平台
getche() conio.h Windows
getch() conio.h Windows
scanf()/scanf_s():和 printf() 类似,scanf() 可以输入多种类型的数据,并且可以使用正则表达式控制输入的范围。从本质上讲,我们从键盘输入的数据并没有直接交给 scanf(),而是放入了缓冲区中,直到我们按下回车键,scanf() 才到缓冲区中读取数据。如果缓冲区中的数据符合 scanf() 的要求,那么就读取结束;如果不符合要求,那么就继续等待用户输入,或者干脆读取失败。
输入类型控制符 说明
%c 读取一个单一的字符
%hd、%d、%ld 读取一个十进制整数,并分别赋值给 short、int、long 类型
%ho、%o、%lo 读取一个八进制整数(可带前缀也可不带),并分别赋值给 short、int、long 类型
%hx、%x、%lx 读取一个十六进制整数(可带前缀也可不带),并分别赋值给 short、int、long 类型
%hu、%u、%lu 读取一个无符号整数,并分别赋值给 unsigned short、unsigned int、unsigned long 类型
%f、%lf 读取一个十进制形式的小数,并分别赋值给 float、double 类型
%e、%le 读取一个指数形式的小数,并分别赋值给 float、double 类型
%g、%lg 既可以读取一个十进制形式的小数,也可以读取一个指数形式的小数,并分别赋值给 float、double 类型
%s 读取一个字符串(以空白符为结束)

输出

puts():只能输出字符串,并且输出结束后会自动换行
putchar():只能输出单个字符
printf():可以输出各种类型的数据,并借助格式控制符控制输出格式(在 printf 中,% 是格式控制符的开头,是一个特殊的字符,不能直接输出;要想输出 %,必须在它的前面再加一个 %,这个时候 % 就变成了普通的字符,而不是用来表示格式控制符了。)(printf() 格式控制符的完整形式:%[flag][width][.precision]type)

输出类型控制符type 说明
%c 输出一个单一的字符
%hd、%d、%ld 以十进制、有符号的形式输出 short、int、long 类型的整数
%hu、%u、%lu 以十进制、无符号的形式输出 short、int、long 类型的整数
%ho、%o、%lo 以八进制、不带前缀、无符号的形式输出 short、int、long 类型的整数
%#ho、%#o、%#lo 以八进制、带前缀、无符号的形式输出 short、int、long 类型的整数
%hx、%x、%lx、%hX、%X、%lX 以十六进制、不带前缀、无符号的形式输出 short、int、long 类型的整数。如果 x 小写,那么输出的十六进制数字也小写;如果 X 大写,那么输出的十六进制数字也大写。
%#hx、%#x、%#lx、%#hX、%#X、%#lX 以十六进制、带前缀、无符号的形式输出 short、int、long 类型的整数。如果 x 小写,那么输出的十六进制数字和前缀都小写;如果 X 大写,那么输出的十六进制数字和前缀都大写。
%f、%lf 以十进制的形式输出 float、double 类型的小数
%e、%le、%E、%lE 以指数的形式输出 float、double 类型的小数。如果 e 小写,那么输出结果中的 e 也小写;如果 E 大写,那么输出结果中的 E 也大写。
%g、%lg、%G、%lG 以十进制和指数中较短的形式输出 float、double 类型的小数,并且小数部分的最后不会添加多余的 0。如果 g 小写,那么当以指数形式输出时 e 也小写;如果 G 大写,那么当以指数形式输出时 E 也大写。
%s 输出一个字符串
%p 表示以十六进制的形式(带小写的前缀)输出数据的地址。如果写作%P,那么十六进制的前缀也将变成大写形式。
flag标志字符 含 义
- -表示左对齐。如果没有,就按照默认的对齐方式,默认一般为右对齐。
+ 用于整数或者小数,表示输出符号(正负号)。如果没有,那么只有负数才会输出符号。
空格 用于整数或者小数,输出值为正时冠以空格,为负时冠以负号。
# 对于八进制(%o)和十六进制(%x / %X)整数,# 表示在输出时添加前缀;八进制的前缀是 0,十六进制的前缀是 0x / 0X。对于小数(%f / %e / %g),# 表示强迫输出小数点。如果没有小数部分,默认是不输出小数点的,加上 # 以后,即使没有小数部分也会带上小数点。

运算符

http://c.biancheng.net/view/161.html

2,流程控制

选择

  • if else
    if(判断条件1){
        语句块1
    } else  if(判断条件2){
        语句块2
    }else  if(判断条件3){
        语句块3
    }else  if(判断条件m){
        语句块m
    }else{
         语句块n
    }
    
    所谓语句块(Statement Block),就是由{}包围的一个或多个语句的集合。如果语句块中只有一个语句,也可以省略{}。
    if 语句嵌套时,要注意 if 和 else 的配对问题。C语言规定,else 总是与它前面最近的 if 配对。
  • switch case
    switch(表达式){
      case 整型数值1: 语句 1; break;
      case 整型数值2: 语句 2; break;
      ......
      case 整型数值n: 语句 n; break;
      default: 语句 n+1; break;
    }
    
    case 后面必须是一个整数,或者是结果为整数的表达式,但不能包含任何变量。
    default 不是必须的。当没有 default 时,如果所有 case 都匹配失败,那么就什么都不执行。

循环

异常处理

3,函数

无参函数

dataType  functionName(){
    //body
}

有参函数

dataType  functionName( dataType1 param1, dataType2 param2 ... ){
    //body
}

函数调用

functionName(param1, param2, param3 ...);

函数声明

  • C语言代码由上到下依次执行,原则上函数定义要出现在函数调用之前,否则就会报错。但在实际开发中,经常会在函数定义之前使用它们,这个时候就需要提前声明。所谓声明(Declaration),就是告诉编译器我要使用这个函数,你现在没有找到它的定义不要紧,请不要报错,稍后我会把定义补上。
  • 函数声明给出了函数名、返回值类型、参数列表(重点是参数类型)等与该函数有关的信息,称为函数原型(Function Prototype)。函数原型的作用是告诉编译器与该函数有关的信息,让编译器知道函数的存在,以及存在的形式,即使函数暂时没有定义,编译器也知道如何使用它。
  • 有了函数声明,函数定义就可以出现在任何地方了,甚至是其他文件、静态链接库、动态链接库等。
  • 对于单个源文件的程序,通常是将函数定义放到 main() 的后面,将函数声明放到 main() 的前面,这样就使得代码结构清晰明了,主次分明。
  • 对于多个文件的程序,通常是将函数定义放到源文件(.c文件)中,将函数的声明放到头文件(.h文件)中,使用函数时引入对应的头文件就可以,编译器会在链接阶段找到函数体。
  • 头文件中包含的都是函数声明,而不是函数定义,函数定义都放在了其它的源文件中,这些源文件已经提前编译好了,并以动态链接库或者静态链接库的形式存在,只有头文件没有系统库的话,在链接阶段就会报错,程序根本不能运行。
dataType  functionName( dataType1 param1, dataType2 param2 ... );
dataType  functionName( dataType1, dataType2 ... );

全局变量与局部变量

  • 定义在函数内部的变量称为局部变量(Local Variable),它的作用域仅限于函数内部, 离开该函数后就是无效的。在 main 函数中定义的变量也是局部变量,只能在 main 函数中使用;同时,main 函数中也不能使用其它函数中定义的变量。main 函数也是一个函数,与其它函数地位平等。
  • 在所有函数外部定义的变量称为全局变量(Global Variable),它的作用域默认是整个程序,也就是所有的源文件,包括 .c 和 .h 文件。如果给全局变量加上 static 关键字,它的作用域就变成了当前文件,在其它文件中就无效了。在一个函数内部修改全局变量的值会影响其它函数,全局变量的值在函数内部被修改后并不会自动恢复,它会一直保留该值,直到下次被修改。
  • C语言规定,在同一个作用域中不能出现两个名字相同的变量,否则会产生命名冲突;但是在不同的作用域中,允许出现名字相同的变量,它们的作用范围不同,彼此之间不会产生冲突。
    • 不同函数内部可以出现同名的变量,不同函数是不同的局部作用域;函数内部和外部可以出现同名的变量,函数内部是局部作用域,函数外部是全局作用域。
    • 函数内部的局部变量和函数外部的全局变量同名时,在当前函数这个局部作用域中,全局变量会被“屏蔽”,不再起作用。也就是说,在函数内部使用的是局部变量,而不是全局变量。

代码块

  • 所谓代码块,就是由{ }包围起来的代码。C语言允许在代码块内部定义变量,这样的变量具有块级作用域;换句话说,在代码块内部定义的变量只能在代码块内部使用,出了代码块就无效了。C语言还允许出现单独的代码块,它也是一个作用域。
  • 遵循 C99 标准的编译器允许在 for 循环条件里面定义新变量,这样的变量也是块级变量,它的作用域仅限于 for 循环内部。
  • 每个C语言程序都包含了多个作用域,不同的作用域中可以出现同名的变量,C语言会按照从小到大的顺序、一层一层地去父级作用域中查找变量,如果在最顶层的全局作用域中还未找到这个变量,那么就会报错。

递归函数

  • 一个函数在它的函数体内调用它自身称为递归调用,这种函数称为递归函数。执行递归函数将反复调用其自身,每调用一次就进入新的一层,当最内层的函数执行完毕后,再一层一层地由里到外退出。
  • 分类:
    • 尾递归:递归调用位于函数体的结尾处
    • 中间递归:发生递归调用的位置在函数体的中间(DFS)
    • 多层递归:在一个函数里面多次调用自己

C标准库

  • 标准C语言(ANSI C)共定义了15 个头文件,称为“C标准库”,所有的编译器都必须支持,如何正确并熟练的使用这些标准库,可以反映出一个程序员的水平。
    • 初:<stdio.h>、<ctype.h>、<stdlib.h>、<string.h>
    • 中:<assert.h>、<limits.h>、<stddef.h>、<time.h>
    • 高:<float.h>、<math.h>、<error.h>、<locale.h>、<setjmp.h>、<signal.h>、<stdarg.h>
    • C library:提供了所有C语言标准函数的原型,并给出了详细的介绍和使用示例,可以作为一部权威的参考手册。

注意:

  • C语言不允许函数嵌套定义;也就是说,不能在一个函数中定义另外一个函数,必须在所有函数之外定义另外一个函数。
  • 函数不能嵌套定义,但可以嵌套调用,也就是在一个函数的定义或调用过程中允许出现对另外一个函数的调用。
  • 在所有函数之外进行加减乘除运算、使用 if...else 语句、调用一个函数等都是没有意义的,这些代码位于整个函数调用链条之外,永远都不会被执行到。C语言也禁止出现这种情况,会报语法错误。(全局变量的定义可以)

4,数组及STL(标准模板库)

数组:

  • 定义
    //一维数组
    dataType  arrayName[length];
    //二维数组
    dataType arrayName[length1][length2];
    
  • 初始化
    //一维数组
    int a[4] = {20, 345, 700, 22};
    int a[10]={12, 19, 22 , 993, 344};  //可以只给部分元素赋值。当{ }中值的个数少于元素个数时,只给前面部分元素赋值。
    int a[] = {1, 2, 3, 4, 5};  //如给全部元素赋值,那么在定义数组时可以不给出数组长度。
    //二维数组
    int a[5][3]={ {80,75,92}, {61,65,71}, {59,63,70}, {85,87,90}, {76,77,85} };  //分段赋值
    int a[5][3]={80, 75, 92, 61, 65, 71, 59, 63, 70, 85, 87, 90, 76, 77, 85};  //连续赋值
    int a[3][3] = {{1}, {2}, {3}};  //可以只对部分元素赋值,未赋值的元素自动取“零”值。
    int a[][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};  //如果对全部元素赋值,那么第一维的长度可以不给出。
    
  • 字符数组
    • 字符数组实际上是一系列字符的集合,也就是字符串(String)。在C语言中,没有专门的字符串变量,没有string类型,通常就用一个字符数组来存放一个字符串。
    • C语言规定,可以将字符串直接赋值给字符数组,但字符数组只有在定义时才能将整个字符串一次性地赋值给它,一旦定义完了,就只能一个字符一个字符地赋值了。
    • 在C语言中,字符串总是以'\0'作为结尾,所以'\0'也被称为字符串结束标志,或者字符串结束符。由" "包围的字符串会自动在末尾添加'\0'。('\0'是 ASCII 码表中的第 0 个字符,英文称为 NUL,中文称为“空字符”。该字符既不能显示,也没有控制功能,输出该字符不会有任何效果,它在C语言中唯一的作用就是作为字符串结束标志。)
    • 需要注意的是,逐个字符地给数组赋值并不会自动添加'\0';当用字符数组存储字符串时,要特别注意'\0',要为'\0'留个位置;这意味着,字符数组的长度至少要比字符串的长度大 1。
      char str[7] = "abc123";
      
    • 逐个字符地为字符数组赋值时,要在在字符串的最后手动添加'\0';更加专业的做法是将数组的所有元素都初始化为“零”值,这样才能够从根本上避免问题。
      char str[30] = {0};  //将所有元素都初始化为 0,或者说 '\0';如果只初始化部分数组元素,那么剩余的数组元素也会自动初始化为“零”值.
      
    • 所谓字符串长度,就是字符串包含了多少个字符(不包括最后的结束符'\0')。例如"abc"的长度是 3,而不是 4。在C语言中,我们使用string.h头文件中的 strlen() 函数来求字符串的长度,它的用法为:
      length strlen(strname);
      
  • 相关算法:
    • 排序:冒泡、快速、选择、插入、归并(合并)
    • 查找:顺序、二分
  • 注意:
    • 数组是一个整体,它的内存是连续。
    • int、char、float 等类型的变量用于 scanf() 时都要在前面添加&,而数组或者字符串用于 scanf() 时不用添加&,它们本身就会转换为地址。
    • 在C语言中,数组一旦被定义后,占用的内存空间就是固定的,容量就是不可改变的,既不能在任何位置插入元素,也不能在任何位置删除元素,只能读取和修改元素,这样的数组称为静态数组。
    • C语言数组是静态的,不能自动扩容,当下标小于零或大于等于数组长度时,就发生了越界(Out Of Bounds),访问到数组以外的内存。如果下标小于零,就会发生下限越界(Off Normal Lower);如果下标大于等于数组长度,就会发生上限越界(Off Normal Upper)。C语言为了提高效率,保证操作的灵活性,并不会对越界行为进行检查,即使越界了,也能够正常编译,只有在运行期间才可能会发生问题。当赋予数组的元素个数超过数组长度时,就会发生溢出(Overflow)。
    • 在 C89 中,必须使用常量表达式指明数组长度;而在 C99 中,可以使用变量指明数组长度。变量的值在编译期间并不能确定,只有等到程序运行后,根据计算结果才能知道它的值到底是什么,所以数组长度中一旦包含了变量,那么数组长度在编译期间就不能确定了,也就不能为数组分配内存了,只有等到程序运行后,得到了变量的值,确定了具体的长度,才能给数组分配内存,我们将这样的数组称为变长数组(VLA, Variable Length Array)。
      • 普通数组(固定长度的数组)是在编译期间分配内存的,而变长数组是在运行期间分配内存的。
      • 变长数组是说数组的长度在定义之前可以改变,一旦定义了,就不能再改变了,所以变长数组的容量也是不能扩大或缩小的,它仍然是静态数组。

STL(标准模板库):

泛型
* 泛型程序设计(generic programming)是一种算法在实现时不指定具体要操作的数据的类型的程序设计方法。所谓“泛型”,指的是算法只要实现一遍,就能适用于多种数据类型,该方法可以大规模的减少程序代码的编写量,让程序员可以集中精力用于业务逻辑的实现。其最成功的应用就是 C++ 的标准模板库(STL)。
* STL,英文全称 standard template library,中文可译为标准模板库或者泛型库,其包含有大量的模板类和模板函数,是 C++ 提供的一个基础模板的集合,用于完成诸如输入/输出、数学计算等功能。
* 如今 STL 已完全被内置到支持 C++ 的编译器中,无需额外安装。STL 就位于各个 C++ 的头文件中,即它并非以二进制代码的形式提供,而是以源代码的形式提供。从根本上说,STL 是一些容器、算法和其他一些组件的集合,这里提到的容器,本质上就是封装有数据结构的模板类,例如 list、vector、set、map 等。
* 一个例子:
  ```C++
  T maxt(T x, T y) {
      return (x > y) ? x : y;
  }
  ```
  * 代码中的 T 是什么呢?很明显,这是一个占位符,更确切的说是一个类型占位符。也就是说,将来在 T 这个位置上的是一个真实、具体的数据类型,至于到底是哪个类型,完全取决于用户的需求。
  * 当然,如果硬要给 T 这种类型占位符也叫做一种数据类型,提供这种想法的发明者称它为泛型(generic type),而使用这种类型占位符的编程方式就被称为泛型编程。
  * 值得一提的是,既然泛型并不是真实的数据类型,那么使用泛型编写的代码也就不是真正的程序实体,只能算是一个程序实体的样板。故此,通常形象的将这种使用了泛型的代码称为模板,由模板生成实际代码的过程称为模板的具体实现。
  * 注意,类型占位符的替换工作,不需要人为操控,可以完全交由计算机来完成,更准确的说,是交由编译器在编译阶段来完成模板的具体实现。
  * 总之一句话,泛型也是一种数据类型,只不过它是一种用来代替所有类型的“通用类型”。在 C++ 中,用以支持泛型应用的就是标准模板库 STL,它提供了 C++ 泛型设计常用的类模板和函数模板。
STL的组成
在 C++ 标准中,STL被组织为 13 个头文件:<iterator><functional><vector><deque><list><queue><stack><set><map><algorithm><numeric><memory><utility>
STL的组成 含义
容器 一些封装数据结构的模板类,例如 vector 向量容器、list 列表容器等。简单的理解容器,它就是一些模板类的集合,但和普通模板类不同的是,容器中封装的是组织数据的方法(也就是数据结构)。STL 提供有 3 类标准容器,分别是序列容器、排序容器和哈希容器,其中后两类容器有时也统称为关联容器。
算法 STL 提供了非常多(大约 100 个)的数据结构算法,它们都被设计成一个个的模板函数,这些算法在 std 命名空间中定义,其中大部分算法都包含在头文件algorithm中,少部分位于头文件numeric中。
迭代器 在 C++ STL 中,对容器中数据的读和写,是通过迭代器完成的,扮演着容器和算法之间的胶合剂。它除了具有对容器进行遍历读写数据的能力之外,还能对外隐藏容器的内部差异,从而以统一的界面向算法传送数据。
函数对象 如果一个类将 () 运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象(又称仿函数)。
适配器 可以使一个类的接口(模板的参数)适配成用户指定的形式,从而让原本不能在一起工作的两个类工作在一起。值得一提的是,容器、迭代器和函数都有适配器。
内存分配器 为容器类模板提供自定义的内存申请和释放功能,由于往往只有高级用户才有改变内存分配策略的需求,因此内存分配器对于一般用户来说,并不常用。
容器种类 功能
序列容器 主要包括 vector 向量容器、list 列表容器以及 deque 双端队列容器。之所以被称为序列容器,是因为元素在容器中的位置同元素的值无关,即容器不是排序的。将元素插入容器时,指定在什么位置,元素就会位于什么位置。
排序容器 包括 set 集合容器、multiset多重集合容器、map映射容器以及 multimap 多重映射容器。排序容器中的元素默认是由小到大排序好的,即便是插入元素,元素也会插入到适当位置。所以关联容器在查找时具有非常好的性能。
哈希容器 C++ 11 新加入 4 种关联式容器,分别是 unordered_set 哈希集合、unordered_multiset 哈希多重集合、unordered_map 哈希映射以及 unordered_multimap 哈希多重映射。和排序容器不同,哈希容器中的元素是未排序的,元素的位置由哈希函数确定。
容器 对应的迭代器类型
array 随机访问迭代器
vector 随机访问迭代器
deque 随机访问迭代器
list 双向迭代器
set / multiset 双向迭代器
map / multimap 双向迭代器
forward_list 前向迭代器
unordered_map / unordered_multimap 前向迭代器
unordered_set / unordered_multiset 前向迭代器
stack 不支持迭代器
queue 不支持迭代器
迭代器分类
  1. 前向迭代器(forward iterator)
    假设 p 是一个前向迭代器,则 p 支持 ++p,p++,*p 操作,还可以被复制或赋值,可以用 == 和 != 运算符进行比较。此外,两个正向迭代器可以互相赋值。
  2. 双向迭代器(bidirectional iterator)
    双向迭代器具有正向迭代器的全部功能,除此之外,假设 p 是一个双向迭代器,则还可以进行 --p 或者 p-- 操作(即一次向后移动一个位置)。
  3. 随机访问迭代器(random access iterator)
    随机访问迭代器具有双向迭代器的全部功能。除此之外,假设 p 是一个随机访问迭代器,i 是一个整型变量或常量,则 p 还支持以下操作:
    p+=i:使得 p 往后移动 i 个元素。
    p-=i:使得 p 往前移动 i 个元素。
    p+i:返回 p 后面第 i 个元素的迭代器。
    p-i:返回 p 前面第 i 个元素的迭代器。
    p[i]:返回 p 后面第 i 个元素的引用。
迭代器定义方式 具体格式
正向迭代器 容器类名::iterator 迭代器名;
常量正向迭代器 容器类名::const_iterator 迭代器名;
反向迭代器 容器类名::reverse_iterator 迭代器名;
常量反向迭代器 容器类名::const_reverse_iterator 迭代器名;
  • 通过定义以上几种迭代器,就可以读取它指向的元素,*迭代器名就表示迭代器指向的元素。其中,常量迭代器和非常量迭代器的分别在于,通过非常量迭代器还能修改其指向的元素。另外,反向迭代器和正向迭代器的区别在于:
  • 对正向迭代器进行 ++ 操作时,迭代器会指向容器中的后一个元素;
  • 而对反向迭代器进行 ++ 操作时,迭代器会指向容器中的前一个元素。

5,多文件编程

  • 不管是标准头文件,还是自定义头文件,都只能包含变量和函数的声明,不能包含定义,否则在多次引入时会引起重复定义错误。

6,文件操作

7,面向对象

8,网络编程

9,多进程/多线程

10,指针/pointer

  • 所谓指针,也就是内存的地址/address(内存中字节的编号);所谓指针变量,也就是保存了内存地址的变量。

  • CPU 访问内存时需要的是地址,而不是变量名和函数名。变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,它们都会被替换成地址。编译和链接过程的一项重要任务就是找到这些名称所对应的地址。

  • &获取变量的地址,*由地址获取变量值,datatype *name 定义指针变量(datatype 和 datatype *是完全不同的数据类型)

  • 指针变量加减运算的结果跟数据类型的长度有关,而不是简单地加 1 或减 1。(如对应int、double、char 数据类型,每次加 1,它们的地址分别增加 4、8、1。)(给进程分配的内存空间是有限的,如果for循环一直增加指针的值并且取对应的值,超过上限会导致读取访问权限冲突)

  • 在C语言中,函数的参数不仅可以是整数、小数、字符等具体的数据,还可以是指向它们的指针。用指针变量作函数参数可以将函数外部的地址传递到函数内部,使得在函数内部可以操作函数外部的数据,并且这些数据不会随着函数的结束而被销毁。

  • C语言允许函数的返回值是一个指针(地址),我们将这样的函数称为指针函数。

  • 指针可以指向一份普通类型的数据,例如 int、double、char 等,也可以指向一份指针类型的数据,例如 int *、double *、char * 等。如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针。

    int a =100;
    int *p1 = &a;
    int **p2 = &p1;
    
  • 一个指针变量可以指向计算机中的任何一块内存,不管该内存有没有被分配,也不管该内存有没有使用权限,只要把地址给它,它就可以指向,C语言没有一种机制来保证指向的内存的正确性。建议对没有初始化的指针赋值为 NULL。void *表示一个有效指针,它确实指向实实在在的数据,只是数据的类型尚未确定,在后续使用过程中一般要进行强制类型转换。(指向NULL的指针会寻址到物理地址为零的内存单元,访问该内存单元会引起系统调用,从而直接引发异常;该内存单元不可用)。

  • 一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似。我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是函数指针。

  • 一些神奇的例子

    #include<stdio.h>
    
    int main() {
    	int a[3] = { 1,2,3 };
    	int *p = a;
    	printf("%d %d\n", a[1], 1[a]);
    	printf("%d %d", *(p + 1), *(a + 1));  //记住这种格式
    }
    

11,结构体

  • C语言结构体(Struct)从本质上讲是一种自定义的数据类型,只不过这种数据类型比较复杂,是由 int、char、float 等基本类型组成的。可以认为结构体是一种聚合类型。
  • 结构体是一种数据类型,可以用它来定义变量;如果只需要定有有限个变量,后面不需要再使用结构体名定义其他变量,那么在定义时也可以不给出结构体名;结构体使用点号.获取单个成员,除了可以对成员进行逐一赋值,也可以在定义时整体赋值。
  • 结构体也有对应的变量、数组、指针
  • ->是一个新的运算符,习惯称它为“箭头”,有了它,可以通过结构体指针直接取得结构体成员;这也是->在C语言中的唯一用途。

12,预处理

  • 在编译之前对源文件进行简单加工的过程,就称为预处理(即预先处理、提前处理)。预处理主要是处理以#开头的命令,例如#include <stdio.h>等。预处理命令要放在所有函数之外,而且一般都放在源文件的前面。预处理是C语言的一个重要功能,由预处理程序完成。当对一个源文件进行编译时,系统将自动调用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。
  • 预处理阶段的工作就是把代码当成普通文本,根据设定的条件进行一些简单的文本替换,将替换以后的结果再交给编译器处理。宏定义可以带有参数,宏调用时是以实参代换形参,而不是“值传送”。
  • #include(文件包含命令) #define(宏定义命令) #undef(取消已定义的宏,终止宏定义命令的作用域) #define(带参宏定义,对于带参宏定义不仅要在参数两侧加括号,还应该在整个字符串外加括号。) #if、#elif、#else 和 #endif (条件编译) #ifdef、#ifndef #error(在编译期间产生错误信息,并阻止程序的编译)
  • 预定义宏:
    • __LINE__:表示当前源代码的行号(而且是去除空行和预处理命令的行数,代码审计天然可行,龟龟);
    • __FILE__:表示当前源文件的名称;
    • __DATE__:表示当前的编译日期;
    • __TIME__:表示当前的编译时间;
    • __STDC__:当要求程序严格遵循ANSI C标准时该标识被赋值为1;
    • __cplusplus:当编写C++程序时该标识符被定义。
    • _WIN32
    • __linux__

13,C语言内存

posted @ 2021-09-07 10:59  tensor_zhang  阅读(420)  评论(0编辑  收藏  举报