C/C++:基本语法看这一篇就够了

前言

本文来自菜鸟教程的C语言教程和C++教程的学习笔记,对其中的示例有所删减与变更,建议以以下两个链接为准。虽说C++是C的扩展,但貌似二者存在差集,而本文只展示了兼容部分。

C 语言教程 | 菜鸟教程 (runoob.com)https://www.runoob.com/cprogramming/c-tutorial.htmlC++ 教程 | 菜鸟教程 (runoob.com)https://www.runoob.com/cplusplus/cpp-tutorial.html

Part1    C

一、认识C

1、C语言的演化历程

可表示为:01-》汇编-》C语言

(1)硬件电路只能表示0、1两个状态,因此最开始编程时,使用的是0101010110101...进行表示,当要做加法时,可以用特定序列(如010101来标识),而对于不同的硬件电路,会有不同的表示方法,因此需要根据硬件电路来定义,并在编程时查表。

(2)当表示加法的硬件电路统一标准后,那么特定序列就会固定下来,这时候人们使用ADD这样的标志来表示特定序列,即使用指令方式来表示,这样就形成了很多指令集,形成汇编语言。

(3)基于汇编语言,发明了C语言,避免了查询指令来进行编程,极大提高了编程效率,这就涉及到编译器的出现,但不同厂家编译器不兼容,会导致同样的代码编译不成功。为了解决这个问题,规定了C标准,如C11。但即使这样,各个厂商为了自身利益,还是产生了很多编译器。

2、C常用编译器

最常用的免费可用的编译器是GNU工具的gcc编译器:在Linux为gcc指令,在Windows 上需要安装 MinGW。这也是搭建环境时需要安装的,不过一般使用IDE,如VS2019集成环境。

3、程序结构

#include <stdio.h>  // 预处理器指令,提供接口调用,告诉编译器在编译前要包含该文件

// 主函数,每个C程序都必须有一个主函数作为入口,程序运行的起点
int main()  // main为函数名,int表示该函数返回int类型,int为整形
{
   /* 注释的方式有2种,编译器将忽略注释内容 */
   printf("Hello, World! \n");  // printf为stdio.h的接口,包含后才能使用
   return 0;  // 终止主函数,返回0,因此当你看到0时,说明程序已经终止运行了
}

4、基本语法

从“3、程序结构”中,我们知道语句结束时,需要使用语句结束符“;”;注释时有2种方式,注释一行“\\”和注释多行“\*\*”;函数或变量会使用函数名来标识,被称为标识符,如main,当然标识符是有规则的;我们还使用了关键字return、int等,他们表示某种特定的功能,因此不能作为标志符来使用;在这个程序中,还有空格的使用,空格分隔语句的各个部分,让编译器能识别语句中的某个元素(比如 int)在哪里结束,下一个元素在哪里开始。

这些有规律的组成就是基本语法,相当于人们说话时的语言组成。因此,每个编写程序的人,口才都应该很好才行,至少要善于组织语言。

二、数据类型

类型分类 类型关键字 存储大小(字节)/用途 值范围(亦即存储大小,因系统类型、系统位数而异)/描述
算数类型 基本类型 整数类型 char 1 -1~127或0~255
unsigned char 1 0~255
signed char 1 -128~127
int 2或4 -32,768~32,767或-2147483648~2147483647
unsigned int 2或4 0~65535或0~4294967295
short 2 -32768~32767
unsigned short 2 0~65,535
long 4 -2147483648~2147483647
unsigned long 4 0~4294967295
浮点类型 float 4 1.2E-38~3.4E+38(精度:6位有效位)
double 8 2.3E-308~1.7E+308(精度:15位有效位)
long double 16 3.4E-4932~1.1E+4932(精度:19位有效位)
枚举类型
void类型 void 函数返回为空 声明函数返回为空值,如void test();
函数参数为空 不传入参数时,可表示为int test(void);
指针指向 void 可指向任何类型的数据,如void *test( size_t size );
派生类型 指针类型
数组类型
结构类型
共用体类型
函数类型

三、变量

从“二、数据类型”的表格中,我们可以通过关键字定义其变量类型,如char c,表示一个字节(八位)的整数类型。特别的,void表示类型的缺失。除此之外,C 语言也允许定义各种其他类型的变量,比如枚举、指针、数组、结构、共用体等等。

变量是一个存储名称,用于告诉编译器在何处创建变量的存储,定义与声明的方式如下表:

变量定义 变量声明 区别
语法结构

type variable;//可以是list
 

type variable_name = value;//可以是list

在变量的基础上增加关键字extern,表示该变量若在别的文件中定义了,则为引入 变量定义的同时也意味着声明,并建立了存储空间,而声明不需要创建存储空间,因为在其他文件(或函数外部)的这个变量已经创建,为外部变量(全局变量),当extern int a=1;则等价于int a=1
隐式初始化 int a;
int a,b;
extern int a;
extern int a,b;
显示初始化 int a=1;
int a=1,b=2;
extern int a=1;
extern int a=1,b=2;

 变量类型与注意事项如下表所示:

描述
局部变量 不会自动初始化,需要赋值使用
全局变量 自动初始化,不同类型初始化如下:
int -》 0
char -》 '\0'
float -》 0
double -》 0
pointer -》 NULL
形式变量 函数形式参数,作用于函数本身,优先级高于全局变量、局部变量

四、常量

从“二、数据类型”的表格中,我们了解到了基本数据类型。而常量可以是任何的基本数据类型,比如整数常量、浮点常量、字符常量,或字符串字面值,也有枚举常量。

定义常量有2种方式,而常量名称通常为大写,如下表:

定义方式 举例 备注
使用 #define 预处理器 #define LENGTH 10; 
使用 const 关键字 const int  LENGTH = 10;
使用 typedef 关键字 typedef unsigned char BYTE; typedef 仅限于为类型定义符号名称,严格意义上算不上常量

字符常量如下表:

转义序列 含义
\\ \ 字符
\' ' 字符
\" " 字符
\? ? 字符
\a 警报铃声
\b 退格键
\f 换页符
\n 换行符
\r 回车
\t 水平制表符
\v 垂直制表符
\ooo 一到三位的八进制数
\xhh . . . 一个或多个数字的十六进制数

五、存储类

存储类定义 C 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前,作用周期与生命周期如下表:

存储类 定义方法 作用域 生命周期
auto auto int a; 局部 调用一次函数(若在函数中定义)后,则自动释放
register register int  a; 寄存器 取决于硬件和实现的限制
static static int a; 局部 值初始化一次,调用一次函数(若在函数中定义)后,不会自动释放相当于拥有全局寿命
extern extern int count; 全局 extern 是用来在另一个文件中声明一个全局变量或函数,拥有全局寿命(程序一直运行,则一直存在)

六、运算符

运算符是一种告诉编译器执行特定的数学或逻辑操作的符号,如下表所示:

名称/运算符 注意事项
算数运算符 加 +
减 -
乘 *
除 / 得到整数部分
取余 %
自增 ++ 放在变量前,先自增1
放在变量后,后自增1
自减 -- 放在变量前,先自减1
放在变量后,后自减1
关系运算符 恒等于 ==
不等于 !=
大于 >
小于 <
大于等于 >=
小于等于 <=
逻辑运算符 与 &&
或 ||
非 !
位运算符 与 & 二进制运算
或 | 二进制运算
异 ^ 二进制运算,两个值不一样则为真
取反 ~ 二进制运算
赋值运算符 等于 =
相加并等于 +=
相减并等于 -=
相乘并等于 *=
相除并等于 /=
取余并等于 %=
左移并等于 <<= 二进制运算
右移并等于 >>= 二进制运算
与并等于 &= 二进制运算
或并等于 |= 二进制运算
异并等于 ^= 二进制运算
杂项运算符 获取变量存储大小 sizeof()
获取变量地址 &
指向一个变量 * 定义指针(int* ptr;)/指针取值(*ptr;)
条件表达式   ?: (如果条件为真)?(则值为 X):(否则值为 Y)

七、判断

满足条件则执行A语句,不满足则执行B语句,这就是判断的作用,如下表所示:

样式1 样式2 备注
if if(判断条件)
{...}
if(判断条件)
{...}
else
{...}
if可嵌套
switch switch(变量)
{
  case 值:
    ...
    break;
}
switch(变量)
{
  case 值:
    ...
    break;
  default:
    ...
}
switch可嵌套
case可多个
? :  (如果条件为真)?(则值为 X):(否则值为 Y)

八、循环

不断地有规律地执行同一代码块,避免不断地写重复代码,这就是循环的作用之一,如下表所示:

样式一 样式二 备注
while while(判断条件)
{...}
do{...}while(判断条件); 样式二至少能执行一次
for for ( init a=1; a<5; a++ )
{...}
三个表达式,任意一个表达式均可省略 for(;;)等价于while(1)

 循环控制语句如下表:

控制语句 描述
break 语句 终止 loop 或 switch 语句,程序流将继续执行紧接着 loop 或 switch 的下一条语句。
continue 语句 引起循环跳过主体的剩余部分,立即重新开始测试条件。
goto 语句 将控制转移到被标记的语句。但是不建议在程序中使用 goto 语句。

 九、函数

函数就是特定功能的代码块,有着特定的输入输出,可理解为接口,如下表所示:

样式一 备注
定义函数 (返回类型) 函数名(参数列表)
{...}
函数的参数可以是
实际值(传值调用)
指针(引用调用)
函数(回调)
函数声明 (返回类型) 函数名(参数列表);
调用函数 函数名(参数列表);

十、数组

数组用于存储一个固定大小的相同类型元素的顺序集合,如下表所示:

样式 备注
声明数组 (类型) 数组名[ 整数值 ];
初始化数组 double balance[5] = {1.0, 2.0, 3.0, 4.0, 5.0}; 整数值可省略,赋值后,默认整数值为赋值元素数量
访问数组元素 数组名[下标];
多维数组 int threedim[5][10][4];
传递数组给函数 void myFunction(int *param){};
void myFunction(int param[10]){};
直接传数组或传指针
从函数中返回数组 static int  r[10];
return r;
直接返回数组

 十一、枚举

枚举用于多个同类型数据一起赋值,如下表所示:

方式一 方式二 方式三
定义枚举变量 enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;
enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
enum
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
转换枚举变量 int a=1;
enum day weekend;
weekend = ( enum day ) a;

 十二、指针

指针用于对内存操作,如下表所示:

形式一 形式二
声明指针 (类型)  *(指针名); (类型)*  (指针名);
指针赋值 指针名=&变量名
变量访问 *指针名
变量赋值 *指针名=值
常用初始化 int  *ptr = NULL;
//ptr 的地址是 0x0

 指针有许多作用,如下表所示:

形式一 形式二 备注
指针运算 支持运算符
++、--、+、-
关系运算
==、<、> ...
运算时,根据数据类型不同,以该类型所占用字节作为一个单位运算
指针数组 int  var[3] = {1, 2, 3};
int *ptr[3];
ptr[1] = &var[1];
*ptr[1]; // 访问变量
   const char *names[] = {
                   "name1",
                   "name2",
                  
   };
names[1]; // 访问变量
*names[]直接赋值,因此指针取值使用names[1],而ptr[1]本身取得是变量地址,因此使用常规取值方式即可。
多重指针 int  *Pt1;
int  **Pt1;
int  ***Pt1;
...
指针形参 void def_name(int *par)
{
   *par = 666;
}
int a;
def_name( &a );
printf(a);
int def_name(int *arr)
{
  int sum = 0;
  for (i = 0; i < size; ++i)
  {
    sum += arr[i];
  }
}
int balance[5] = {1000, 2, 3, 17, 50};
def_name(balance)
可传内存地址、可传数组
返回指针 int * def_name()
{...}
int * getRandom( )
{
   static int  arr[2]={1,2};
   return arr;
}

函数指针 int def_name(int a)
{
...
}
int a=1;
int (* p)(int) = &def_name; // &可以省略
p(a);
结构指针 struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
struct Books *struct_pointer;
struct_pointer = &Book1;
struct_pointer->title; // 访问结构成员

十三、回调函数

A函数被当作参数传入B函数,那么A函数被叫做回调函数,如下表所示:

形式一
回调函数 int one_def(void)
{
    return 0;
}

void def_name(int (*get_def)(void))
{
    get_def();
}
def_name(one_def);

十四、字符串

字符串实际上是使用空字符 \0 结尾的一维字符数组,初始化时默认添加\0到末尾,操作字符串时,可使用stdio.h头文件,提供函数如下表:

作用
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 的第一次出现的位置

 十五、结构体

用户自定义数据类型,可存储不同类型的数据项,如下表所示:

形式一 形式二 形式三 形式四 形式五 形式六
定义结构 struct
{
    int a;
    char b;
    double c;
} s1;
struct SIMPLE
{
    int a;
    char b;
    double c;
};
struct SIMPLE t1, t2[20], *t3;
typedef struct
{
    int a;
    char b;
    double c;
} Simple2;
Simple2 u1, u2[20], *u3;
struct COMPLEX
{
    char string[100];
    struct SIMPLE a;
};
struct NODE
{
    char string[100];
    struct NODE *next_node;
};
struct B;
 
struct A
{
    struct B *partner;
    //other members;
};
 
struct B
{
    struct A *partner;
    //other members;
};
初始化结构 struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};
访问结构 strcpy( Book1.title, "C Programming");
book.book_id = 6495407;
结构作为函数参数 void printBook( struct Books book );

 十六、共用体

在相同的内存位置存储不同的数据类型,需要使用共用体,如下表所示:

形式一
定义共用体 union Data
{
   int i;
   float f;
   char  str[20];
} data;

union Data data;       
printf( "Memory size occupied by data : %d\n", sizeof(data));  // 20
访问共用体 data.i = 10;
data.f = 220.5;
strcpy( data.str, "C Programming");
printf( "data.i : %d\n", data.i); //因为共用存储,因此data.i的值并不是10

十七、位域

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

形式一 形式一 备注
位域声明 struct
{
  int a : 1;
  int b : 2;
} bit_name;
struct name
{
  int a : 1;
  int b : 2;
  int   : 3;
} bit_name;
这样设计规定了a的存储空间为1,b的存储空间为2,空域占了3字节不能被使用
使用位域 bit_name.a=1; // 需要在范围内赋值

十八、预处理器

 预处理器是一个文本替换工具,指示编译器在实际编译之前完成所需的预处理,如下表所示:

形式一 备注
预处理指令 #define #define MAX_ARRAY_LENGTH 20 定义宏/声明变量
#include #include <stdio.h>
#include "myheader.h"
包含一个源代码文件
#undef #undef  FILE_SIZE
#define FILE_SIZE 42
取消已定义的宏
#ifdef #ifdef DEBUG
   /* Your debugging statements here */
#endif
如果宏已经定义,则返回真
#ifndef #ifndef MESSAGE
   #define MESSAGE "You wish!"
#endif
如果宏没有定义,则返回真
#if 如果给定条件为真,则编译下面代码
#else #if 的替代方案
#elif 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个 #if……#else 条件编译块
#error 当遇到标准错误时,输出错误消息
#pragma 使用标准化方法,向编译器发布特殊的命令到编译器中
预定义宏 __DATE__ 来源stdio.h头文件,可直接使用的宏,但不能修改,代表当前日期
__TIME__ 当前时间
__FILE__ 当前文件名
__LINE__ 当前行号
__STDC__ 当编译器以 ANSI 标准编译时,则定义为 1
预处理器运算符 \ 一条宏可使用\换行
# #define  message_for(a, b)  \
    printf(#a " and " #b ": We love you!\n")
可对message_for进行传参
## #include <stdio.h>

#define tokenpaster(n) printf ("token" #n " = %d", token##n)

int main(void)
{
   int token34 = 40;
  
   tokenpaster(34);
   return 0;
}

// token34 = 40
允许在宏定义中两个独立的标记被合并为一个标记
defined() #include <stdio.h>

#if !defined (MESSAGE)
   #define MESSAGE "You wish!"
#endif

int main(void)
{
   printf("Here is the message: %s\n", MESSAGE); 
   return 0;
}
如果MESSAGE被定义过,则defined返回真
参数化的宏 #include <stdio.h>

#define MAX(x,y) ((x) > (y) ? (x) : (y))

int main(void)
{
   printf("Max between 20 and 10 is %d\n", MAX(10, 20)); 
   return 0;
}
// Max between 20 and 10 is 20
使用参数化的宏来模拟函数

十九、头文件

头文件能够提供接口供外部调用,如下表所示:

形式一 形式二 备注
定义头文件 #ifndef HEADER_FILE
#define HEADER_FILE

...

#endif
引用头文件 #include <file> #include "file" <>用于标准库包含,""用于私有库包含
有条件引用 #if SYSTEM_1
   # include "system_1.h"
#elif SYSTEM_2
   # include "system_2.h"
#elif SYSTEM_3
   ...
#endif
 #define SYSTEM_H "system_1.h"
 ...
 #include SYSTEM_H
指定在不同的操作系统上使用的配置参数可以使用宏的条件语句,这样兼容性更强

二十、强制类型转换

强制类型转换是把变量从一种类型转换为另一种数据类型,如下表所示:

形式一 备注
强制类型转换 int sum = 17, count = 5;
double mean;
mean = (double) sum / count;
char可以和int直接相加(隐式转换),这中间产生的类型转换,被称为整数提升
常用的算数转换 算术中产生的隐式转换 转换时将遵从如下优先级:
long double
double
float
unsigned long long
long long
unsigned long
long
unsigned int
int

二十一、递归

递归指的是在函数的定义中使用函数自身的方法,如下表所示:

形式一 备注
数的阶乘 #include <stdio.h>
 
double factorial(unsigned int i)
{
   if(i <= 1)
   {
      return 1;
   }
   return i * factorial(i - 1);
}
int  main()
{
    int i = 15;
    printf("%d 的阶乘为 %f\n", i, factorial(i));
    return 0;
}
递归函数的要点是定义一个从函数退出的条件
斐波那契数列 #include <stdio.h>
 
int fibonaci(int i)
{
   if(i == 0)
   {
      return 0;
   }
   if(i == 1)
   {
      return 1;
   }
   return fibonaci(i-1) + fibonaci(i-2);
}
 
int  main()
{
    int i;
    for (i = 0; i < 10; i++)
    {
       printf("%d\t\n", fibonaci(i));
    }
    return 0;
}

二十二、可变参数

要给函数传入可变数量的参数,如下表所示:

形式一 备注
可变参数 int func(int, ... ) {...} 需要使用 stdarg.h 头文件,该文件提供了实现可变参数功能的函数和宏
例子 #include <stdio.h>
#include <stdarg.h>
 
double average(int num,...)
{
 
    va_list valist;
    double sum = 0.0;
    int i;
 
    /* 为 num 个参数初始化 valist */
    va_start(valist, num);
 
    /* 访问所有赋给 valist 的参数 */
    for (i = 0; i < num; i++)
    {
       sum += va_arg(valist, int);
    }
    /* 清理为 valist 保留的内存 */
    va_end(valist);
 
    return sum/num;
}
 
int main()
{
   printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
   printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}

二十三、命令行参数

想要从外部控制程序,而不是在代码内对这些值进行硬编码时,如下表所示:

形式一 备注
命令行参数 #include <stdio.h>

int main( int argc, char *argv[] ) 
{
   if( argc == 2 )
   {
      printf("The argument supplied is %s\n", argv[1]);
   }
   else if( argc > 2 )
   {
      printf("Too many arguments supplied.\n");
   }
   else
   {
      printf("One argument expected.\n");
   }
}
argc 是指传入参数的个数,argv[] 是一个指针数组,指向传递给程序的每个参数

Part2    C++

 一、认识C++

1.、C与C++

C++是C语言的超集,即包含C语言全部语法,并基于此进行了扩展。相对C语言,C++面向对象,而C面向过程,即使如此,C++本质上是C。C++的程序文件后缀为cpp,而C程序文件的后缀是c。

2、基本语法

在C的基础上,引入了对象、类、方法、即时变量,而方法就是C函数。而即时变量指的是对象内的变量,他跟随着对象的生命周期。在C++中引入了命名空间的概念,常用的命名空间有”using namespace std; “,为的是避免地会出现变量或函数的命名冲突。

二、差异

这里的差异指的是C++基于C的新增,即C有的,C++都有(貌似存在差集,只能说以前C++是        C的超集,现在语法上存在差集,以后可能会渐行渐远),就不再重复累述。

1、数据类型

(1)布尔型

bool :有两个值,true 值代表真,false 值代表假

(2)宽字符型

wchar_t  : 一个宽字符暂用2 或 4 个字节,如果常量以 L(仅当大写时)开头,则表示它是一个宽字符常量(例如 L'x'),此时它必须存储在 wchar_t 类型的变量中。否则,它就是一个窄字符常量(例如 'x'),此时它可以存储在 char 类型的简单变量中。

2、常量

新增2个常量修饰符:

(1)volatile

修饰符 volatile 告诉编译器不需要优化volatile声明的变量,让程序可以直接从内存中读取变量。对于一般的变量编译器会对变量进行优化,将内存中的变量值放在寄存器中以加快读写效率。

(2)restrict

由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。

3、存储类

(1)mutable

mutable 说明符仅适用于类的对象,它允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改。

(2)thread_local

使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。

4、运算符

(1)二进制运算符(在C中也可用,这里做补充)

<< : 左移运算符

>> :右移运算符

(2)逗号运算符

 ,  : 会顺序执行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最后一个表达式的值。

(3)成员运算符

.  :用于引用类、结构和共用体的成员

(4)强制转换运算符

const_cast 运算符用于修改类型的 const / volatile 属性。除了 const 或 volatile 属性之外,目标类型必须与源类型相同。这种类型的转换主要是用来操作所传对象的 const 属性,可以加上 const 属性,也可以去掉 const 属性。

dynamic_cast 在运行时执行转换,验证转换的有效性。如果转换未执行,则转换失败,表达式 expr 被判定为 null。dynamic_cast 执行动态转换时,type 必须是类的指针、类的引用或者 void*,如果 type 是类指针类型,那么 expr 也必须是一个指针,如果 type 是一个引用,那么 expr 也必须是一个引用。

reinterpret_cast 运算符把某种指针改为其他类型的指针。它可以把一个指针转换为一个整数,也可以把一个整数转换为一个指针。

static_cast 运算符执行非动态转换,没有运行时类检查来保证转换的安全性。例如,它可以用来把一个基类指针转换为派生类指针。

5、函数

(1)匿名函数

形式一 形式二 形式三 形式四
匿名函数 []{ ++global_x; }  [](int x, int y){ return x < y ; } [](int x, int y) -> int { int z = x + y; return z + x; } [this]() { this->someFunc(); }();

6、字符串

(1)string类类型

由C++ 标准库提供,引入(#include <string>),使用(string str1 = "runoob";)。

7、引用

引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。形式如 int& r = i;  表明r与i共用存储地址。

引用 样式一 备注
声明引用 int i = 17;
int&  r = i;
引用与指针的不同:
1、不存在空引用。引用必须连接到一块合法的内存
2、一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象
3、引用必须在创建时被初始化。指针可以在任何时间被初始化

三、类与对象

类是对C函数的二次封装,让C函数有了集合,形成了对象,如下表所示:

形式一 形式二
定义类 class Box
{
   public:
      double length;      // 长度
      double breadth;     // 宽度
      double height;      // 高度
  
      double getVolume(void)
      {
         return length * breadth * height;
      }
};
class Box
{
   public:
      double length;      // 长度
      double breadth;     // 宽度
      double height;      // 高度
      double getVolume(void)
};
double Box::getVolume(void)
{
    return length * breadth * height;
}
声明类 Box myBox;          // 创建一个对象
使用类 myBox.getVolume();  // 调用该对象的成员函数

类的访问有特定的修饰符,如下表所示:

访问方式 备注
public 可供外部直接访问
private 外部不能访问 没有被修饰的成员均为私有成员
protected 外部不能访问,但子类能访问

 类的构造函数会在创建类的新对象时执行,而析构函数在销毁类的对象时执行,如下表所示:

样式一 样式二 备注
构造函数 class Line
{
public:
Line();  //构造函数
}

Line::Line(void)
{}
class Line
{
public:
Line();  //构造函数
}

Line::Line( double len): length(len)
{}
构造函数的名称与类的名称是完全相同的,可带参数;带参数时,如果没有像样式二那样已经传参,那么初始化要输入参数,如:Line line(10.0);多个字段参数化时,可以采用如下方式:
C::C( double a, double b, double c): X(a), Y(b), Z(c){}
析构函数 class Line
{
public:
~Line();  //析构函数
}

Line::~Line(void)
{}
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。

拷贝构造函数在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象,类似于python中的单例模式,如下表所示:

样式一 备注
拷贝构造函数 class Line
{
   public:
      int getLength( void );
      Line( int len );             // 简单的构造函数
      Line( const Line &obj);      // 拷贝构造函数
      ~Line();                     // 析构函数
 
   private:
      int *ptr;
};
obj 是一个对象引用,该对象是用于初始化另一个对象的

 类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。如下表所示:

样式一 备注
友元函数 class Box
{
public:
   friend void printWidth( Box box );
};

void printWidth( Box box ){}
友元函数不是任何类的成员函数

 内联函数在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。如下表所示:

样式一
内联函数 inline int Max(int x, int y)
{
   return (x > y)? x : y;
}

 this 指针来访问自己的地址,类似于python中的self,如下表所示:

样式一
this指针 class Box
{
   public:
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
      }
      double Volume()
      {
         return length * breadth * height;
      }
      int compare(Box box)
      {
         return this->Volume() > box.Volume();
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};

当一个指针指向类时,访问方式使用 -> 符号,如下表所示:

样式一
指向类的指针 class Box
{
   public:
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
      }
      double Volume()
      {
         return length * breadth * height;
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};

Box Box1(3.3, 1.2, 1.5);    // Declare box1
Box Box2(8.5, 6.0, 2.0);    // Declare box2
Box *ptrBox;                // 指向类的指针
ptrBox = &Box1;
ptrBox->Volume();  // 访问方式

 类的静态成员在所有对象中是共享的,如下表所示:

样式一
静态成员变量 static int objectCount;
静态成员函数 static int getCount()
      {
         return objectCount;
      }

 四、继承

继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易,如下表所示:

样式一 样式二 备注
继承 // 基类
class Animal {
    // eat() 函数
    // sleep() 函数
};


//派生类(子类)
class Dog : public Animal {
    // bark() 函数
};
// 基类 Shape
class Shape
{
};
 
// 基类 PaintCost
class PaintCost
{
};
 
// 派生类
class Rectangle: public Shape, public PaintCost
{
   public:
      int getArea()
      {
         return (width * height);
      }
};
继承方式有3种,public、protected 或 private,缺省则默认private

我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:

  • 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有保护成员来访问。
  • 保护继承(protected): 当一个类派生自保护基类时,基类的公有保护成员将成为派生类的保护成员。
  • 私有继承(private):当一个类派生自私有基类时,基类的公有保护成员将成为派生类的私有成员。

一个派生类继承了所有的基类方法,但下列情况除外:

  • 基类的构造函数、析构函数和拷贝构造函数。
  • 基类的重载运算符。
  • 基类的友元函数。

五、重载运算符和重载函数

重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。如下表所示:

样式一 备注
重载 class Box
{
   public:
 
      double getVolume(void)
      {
         return length * breadth * height;
      }
      void setLength( double len )
      {
          length = len;
      }
 
      void setBreadth( double bre )
      {
          breadth = bre;
      }
 
      void setHeight( double hei )
      {
          height = hei;
      }
      // 重载 + 运算符,用于把两个 Box 对象相加
      Box operator+(const Box& b)
      {
         Box box;
         box.length = this->length + b.length;
         box.breadth = this->breadth + b.breadth;
         box.height = this->height + b.height;
         return box;
      }
   private:
      double length;      // 长度
      double breadth;     // 宽度
      double height;      // 高度
};
类似于python的魔术方法

六、多态

多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。如下表所示:

样式一 样式二
多态 class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      virtual int area()  // 虚函数(动态链接),允许多态(允许子类重写该函数)
      {
         cout << "Parent class area :" <<endl;
         return 0;
      }
};
class Rectangle: public Shape{
   public:
      Rectangle( int a=0, int b=0):Shape(a, b) { }
      int area ()
      {
         cout << "Rectangle class area :" <<endl;
         return (width * height);
      }
};
class Triangle: public Shape{
   public:
      Triangle( int a=0, int b=0):Shape(a, b) { }
      int area ()
      {
         cout << "Triangle class area :" <<endl;
         return (width * height / 2);
      }
};
class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      // 纯虚函数,没有具体实现
      virtual int area() = 0;
};

猜你喜欢

转载自blog.csdn.net/weixin_43431593/article/details/124785262