目录
一, C++的概述
1 C++的特征
所谓的C++,是对C的扩展;c++语言在 c 语言的基础上添加了面向对象编程和泛型编程的支持。
1. C++ 继承C语言的思想:具备C语言面向过程的思想,语言高效、简洁,具有可移植性;
2. C++ 具备面向对象程序设计思想;
3. 模板支持的泛型编程;
2 C++程序的编辑、编译和执行
由于C++继承了C语言的特性,也就是说C语言程序可以作为C++程序。
在Linux系统中,GNU工具中提供了C++编译器:g++,整个程序的编译过程依然分为4个阶段:
整体编译:g++ 源程序文件 可以使用-o指定输出可执行文件名称,如果未指定则默认生成可执行文件 a.out
1. 预处理阶段
实现预处理指令的执行:头文件的展开、宏(标识符常量和标识符语句)定义的替换、语句的选择编译
g++ -E hello.c -o hello.i
2. 编译阶段
实现预处理后程序语法的检测,并生成汇编指令;
g++ -S hello.i -o hello.s
3. 汇编阶段
实现将汇编指令翻译为二进制机器指令;
g++ -C hello.s -o hello.o
4. 链接阶段
实现将程序中所有的.o文件进行链接、依赖库链接和启动程序连接;
g++ hello.o -o hello
3 第一个C++源程序
#include <iostream> /* 添加头文件:标准库头文件(提供标准输入输出流) */
using namespace std; /* 做名称空间域声明,声明后可以直接对名称空间域中的函数和变量符号进行直接访问 */
int main()
{
/* cout:标准输出流,std::cout;endl:是换行标识,定义在std::endl; */
cout << "hello world" << endl; /* 等价于:printf("hello world"); */
return 0;
}
1. C++源程序后缀:
源程序文件可以使用.c、.cxx、.cpp
在C++源程序设计过程中,一般情况将源程序文件后缀设置为.cpp(可移植性更好)。
2. 头文件的包含:
3. namespace 做名称空间域的声明
在C++中新增名称空间域,避免同一个程序中的多个同名变量和函数的访问。
4. cout和endl
cout是标准C++库中所提供的标准输出流,在名称空间域std中定义;
endl是标准C++库中提供的换行标识,同时刷新缓冲区;在名称空间域std中定义;
4 面向对象程序设计思想
4.1 面向对象程序设计思想初始
1. 面向过程程序设计思想
所谓的面向过程程序设计思想,指的是以解决问题的思路为步骤,按照步骤先后顺序依次执行逐一解决问题的过程。
面向过程编程思想的核心:功能分解,自顶向下,逐层细化(程序=数据结构+算法)。
2. 面向过程程序设计思想
所谓面向对象编程(Object-Oriented Programming)简称 OOP 技术
任何类对象都具有一定的属性和操作行为,可以将属性抽象为具体数据类型变量和常量存储、将操作行为抽象为具体的函数及其算法。
所谓的对象 = 数据结构 + 算法;
程序 = 对象 + 对象 + ……
4.2 面向对象程序设计思想的核心
1. 封装
所谓的封装,实质是将相同的对象的属性抽象为具体数据类型的成员变量和对象的行为抽象为具体的成员函数。并同时使用访问权限修饰封装的过程。
2. 继承
所谓的继承,表示类与类之间的关系,在构造新的类的时候,如果新构造的类对象和原有类型对象之间满足:is关系的时候,让新构造类对象继承原有类对象的属性和行为的过程。
继承可以有效实现代码重用,避免代码和数据冗余。
3. 多态
所谓的多态,指的是“一个接口,多种方法”。在程序运行过程中决定接口的具体行为。
二, C++对C的扩展
1 作用域访问运算符(::)
在C++中增加了作用域访问运算符(::),可以修饰函数和变量的作用域,以实现在同一个程序中对不同作用域下同名成员变量和成员函数的访问。
具体的语法格式:
作用域::函数和变量符号
1) 作用域可以是全局作用域、具备作用域、名称空间域和类域;
2) 函数和变量符号、可以是C函数和变量、类成员函数和变量以及名称空间域成员函数和变量;
1. 全局变量和局部变量
作用域省略:此时默认作用为全局域且仅为全局域;
作用域和作用域访问运算符省略:模块内使用,
1) 模块内有定义,此时访问的模块内域中的函数和变量;
2) 模块内未定义,此时访问全局的函数和变量;
#include <stdio.h>
int a = 1; /* 定义一个全局作用的变量 */
int main()
{
int a = 3; /* 在局部模块内定义了和全局同名的局部变量和函数,局部覆盖全局 */
/* 省略作用域和作用域运算符的时候,模块内有局部变量则覆盖全局变量,访问的是局部变量,否则模块内没有局部变量则访问全局变量 */
printf("a = %d\n", a);
/* 只省略作用域而未省略作用访问运算符的时候,默认作用域为全局域且只能为全局域 */
printf("::a = %d\n", ::a);
}
2. 作用域为类域或者名称空间域
所谓的变量和函数是指定类域或者名称空间域中的成员和变量;
#include <stdio.h>
int a = 1;
namespace myspace {
int a = 4; /* 定义变量a的作用域为myspace名称空间 */
};
int main()
{
int a = 3;
printf("a = %d\n", a);
printf("::a = %d\n", ::a);
/* 在作用域和作用域运算符不省略的时候,作用域为名称空间域和类域,所访问的是指定作用域中的变量和函数 */
printf("myspace::a = %d\n", myspace::a);
}
2 名称空间域
所谓名称空间域,指的是将特殊的符号常量、变量、函数、结构体、枚举等数据量,使用关键字namespace的名称空间域进行封装,此时数据符号只能在名称空间域中和通过名称空间域进行访问访问,从而解决程序中因为同名导致访问函数或者变量冲突的问题。名称空间域也称为命名空间/名字空间/名称空间
2.1 创建名称空间域
1. 语法格式
namespace 名称空间域的名称 {
/* 符号常量、变量、函数、结构体、枚举等 */
}
2. 创建名称空间域实例
a) 创建有名命名空间
#include <stdio.h>
int a = 3; /* a作用域为全局域 */
namespace myspace {
enum {
Frist = 1,
second
};
/* 名称空间域中定义成员变量 */
int a = 5; /* C++中增加名称空间域,a的作用域为名称空间域:可以在名称空间域内访问,也可以通过名称空间域访问 */
/* 名称空间域中定义和声明成员函数 */
void show(void)
{
printf("myspace -> a = %d, second = %d\n", a, second); /* 在名称空间域内访问 */
}
/* 名称空间域中声明成员函数,在名称空间域外实现定义 */
void test(void);
}
/* 在名称空间域外部实现名称空间域中函数的定义 */
void myspace::test(void)
{
printf("myspace::test()\n");
}
/* 名称空间域具有开放性:如果新创建的名称空间域已经存在,则此时是在原同名名称空间域中新增成员*/
namespace myspace {
int b = 1;
}
/* 名称空间域可以嵌套创建 */
namespace myspace {
/* 在myspace名称空间域中创建Aspace名称空间域 */
namespace Aspace {
int a = 33;
}
/* 在myspace名称空间域中创建Bspace名称空间域 */
namespace Bspace {
int a = 44;
}
}
void create_namespace(void)
{
/* 名称空间域的创建只能全局创建,不能局部创建 */
namespace A { /* error:‘namespace’ definition is not allowed here */
int a = 111;
}
}
int main()
{
int a = 4; /* 作用域为局部作用域,只能在main函数模块内访问 */
printf("myspace::a = %d\n", myspace::a); /* 通过名称空间域访问名称空间域中所定义的变量 */
myspace::show();
// printf("second = %d\n", second); /* error, */
myspace::test();
printf("myspace::b = %d\n", myspace::b);
printf("myspace::Aspace::a = %d, myspace::Bspace::a = %d\n", myspace::Aspace::a, myspace::Bspace::a);
}
b) 创建无名命名空间
#include <iostream>
using namespace std;
int a = 3;
/* 创建无名命名空间域:
* 无名命名空间中的成员只能在当前文件中访问,相当于使用s
tatic修饰;
*
如果成员符号和全局变量或者函数等符号相同,访问出现异常
*/
namespace {
int a = 4;
int b = 5;
}
int main()
{
//cout << a << endl; /* error,
此时的变量可以访问到全局变量a和无名命名空间中的a */
cout << b << endl;
}
2.2 已有名称空间域成员访问
名称空间域中成员访问
1) 使用作用域访问运算符访问:
名称空间域::成员变量
名称空间域::成员函数();
std::cout << "namespace" << std::endl;/* 访问std名称空间域中的cout和endl */
2) 先声明名称空间域,然后直接符号访问
using namespace std; /* 对整个名称空间域进行声明 */
cout << "namespace" << endl; /* 已声明名称空间域中的符号可以直接使用符号访问 */
3) 声明名称空间域中的符号,然后直接符号访问
using std::cout; /* 对名称空间域中的单个符号进行声明 */
cout << "namespace" << std::endl; /* 已声明符号可以直接访问,未声明符号需要通过作用域访问运算符访问 */
2.3 多文件名称空间域创建
在实际应用过程中,可能多个文件中的成员在同一个名称空间域中,在创建空间域的时候注意:
1) 对于名称空间域中的成员函数和成员变量的定义不能在头文件中定义,只能在源程序中定义,否则出现重复定义;
2) 在头文件中创建名称空间域的时候,名称空间域中只能用于函数的声明。
3) 名称空间域中成员变量只能在源文件中定义,此时的作用只能作用域所定义的文件中名称空间域中;
1. 名称空间域的创建的头文件:myspace.h
#ifndef _MYSPACE_H_
#define _MYSPACE_H_
#include <iostream>
using namespace std;
/* 创建名称空间域 */
namespace myspace {
/* 在名称空间域中进行成员函数的声明 */
void test(void);
# if 0
/* 在头文件中做成员变量和函数的定义,可能出现重复定义错误 */
void show()
{
cout << "myspace::show()" << endl;
}
int a;
#endif
}
#endif
2. 源程序文件:myspace.cpp
#include <iostream>
#include "myspace.h"
using namespace std;
/* 定义名称空间域中的成员函数 */
void myspace::test(void)
{
cout << "myspace::test(void)" << endl;
}
namespace myspace {
int b = 34;
}
3. 应用程序:app.cpp
#include <iostream>
#include "myspace.h"
using namespace std;
using namespace myspace;
namespace myspace {
int c = 112;
}
int main()
{
test();
#if 0
cout << "a = " << a << endl;
cout << b << endl;
cout << myspace::b << endl; /* error:只能在定义源程序文件中访问 */
#endif
cout << c << endl;
}
3 语法检测增强
3.1 同名未初始化全局变量
在C语言语法中,同名变量的出现表示变量的声明;而在C++语言中,同名变量的出现表示未重复定义;
#include <stdio.h>
int a = 34;
/* 同名未初始化全局变量
* 在C语言程序中,如果出现同名未初始化全局变量则表示全局变量的声明
* 在C++语言程序中,如果出现同名未初始化全局变量则表示全局变量重复定义出现语法错误,
*/
int a;
int main()
{
printf("a = %d\n", a);
}
3.2 数据类型语法增强
#include <stdio.h>
int main()
{
int a = 0x12345678;
char c = a;
/* 小数据类型指针指向大数据类型空间:
* C语言中,语法警告;C++中语法错误
*/
char *p = &a;
}
3.3 新增bool数据类型
在C语言中所使用的bool数据类型不能访问,需要添加头文件#include <stdbool.h>才能访问;也就是说在C标准库中未声明bool数据类型,在C标准库之外进行bool数据类型的声明。
在C++语言中,直接新增bool数据类型及其数据符号true和false;可以直接访问。
3.4 函数的参数问题
#include <stdio.h>
/* 在函数声明和定义的时候,没有形参列表(也没有void)的时候:
* 在C语言中,表示为可变形参,在函数调用的时候,传递的参数个数和参数的数据类型都是可变的(传递任意个数的任意数据类型实参数据值赋值给形参);但是函数无形参接收。
* 在C++中,表示函数无形参列表,形参列表等价于void;在函数调用的时候不能传递参数值。
*/
void test()
{
}
/* 在函数声明和定义的时候,函数的形参列表中的形参只有形参变量名,而没有形参数据类型
* 在C语言中,无语法错误,默认将形参数据类型为int数据类型变量;
* 在C++中,语法错误。
*/
void test1(a)
{
}
int main()
{
test();
test('a');
test("test");
test(1,2,3,4,5);
test1(2);
test1(2, 3);
test1("abc");
}
3.5 三目运算符增强
1. 左值和右值
在含有赋值运算符的表达式中,所谓的左值指的是赋值运算符左边的变量及其表达式称为左值,左值一定是可以被修改的量;所谓的右值指的是赋值运算符右边的变量、常量及其表达式值。
2. 三目运算符
条件表达式 ?表达式1 :表达式2;
执行过程:执行条件对表达式的结果进行判断
1) 如果结果为true,则执行表达式1;
2) 否则如果结果为false,则执行表达式2;
3. C和C++中的三目运算符
三目运算符在C语言中,表达式1和表达式2不能作为左值,而在C++中对表达式1和表达式2变量进行引用作为左值。
3.6 const 增强
1. C语言中的const变量
const修饰的变量为只读变量,不可以直接修改,可以使用指针做间接修改const修饰变量的值。
#include <stdio.h>
int main()
{
const int a = 3; /* 给变量a分配内存空间,a访问权限为 read-only,不能直接通过变量a修改存储空间的数据值 */
int *p = &a; /* p存储变量a存储空间地址,可以通过指针变量p间接修改a存储空间数据值 */
printf("a = %d, *p = %d\n", a, *p);
// a = 5; /* error: assignment of read-only variable ‘a’ */
*p = 10;
printf("a = %d, *p = %d\n", a, *p);
}
2. C++中的const变量
a) const常量
const修饰的变量,称为常变量,也称为常量,不能被修改(即不可以直接通过变量本身修改,也不可以通过指针间接修改)。
#include <stdio.h>
int main()
{
const int a = 3; /* 不会给const变量分配空间,变量a存储在符号常量表中 */
int *p = &a; /* 给符号变量a取地址运算&a,开辟临时存储空间,变量a的数据值,将临时存储空间地址赋值给指针变量p */
printf("a = %d, *p = %d\n", a, *p);
// a = 5; /* error: assignment of read-only variable ‘a’ */
*p = 10; /* 修改临时存储空间中的数据值 */
printf("a = %d, *p = %d\n", a, *p); /* a = 3, *p = 10*/
}
b) C++中const常量和#define常量区别:
共同点:
const修饰的常量和#define定义的符号常量都是常量,只读不能被修改。
不同点:
1) 类型检测:const修饰的常量有数据类型,在编译过程中做类型安全检测;而#define定义的标识符常量无类型。
2) 作用域:const修饰的常量有作用域,而#define定义标识符常量无作用域限制;
在C++中常量,建议使用const常量实现。
3.7 struct增强
struct是一个关键字,所修饰的数据类型为结构体数据类型
1. 在C语言中结构体成员
C语言中的结构体用于对数据成员的封装:成员可以数据成员变量和指针(数据类型指针和函数指针)成员变量。
2. 在C++中结构体成员增强
a) 成员可以C语言中的数据成员和指针成员之外,还可以将函数作为成员;
b) 使用结构体数据类型(定义变量和指针)的时候,可以省略struct关键字,实质就是C++中的类。
c) 成员增加访问权限修饰:
private:私有成员,只能在当前类中访问;
protected:受保护成员,可以在当前类及其派生类中访问;
public:公有成员,可以在任意位置访问。
#include <stdio.h>
#if 0
struct Stu {
int num;
char name[32];
void (*func_p)(void);
};
#else
struct Stu {
private: /* 1. 在C++中成员新增访问权限修饰: */
int num;
protected:
char name[32];
public:
void (*func_p)(void);
/* 2. 在C++中结构体成员可以是函数 */
void show(void)
{
printf("show()");
}
};
int main()
{
struct Stu stu;
Stu stu1; /* 3.C++中结构体数据类型可以省略关键字struct */
}
4 引用(reference)
所谓的引用,指的是对变量进行引用访问,也称为给变量取别名。变量和引用为同一内存空间。
4.1 引用定义
语法:
数据类型 & 引用变量名称;
数据类型:引用变量的数据类型,需要和被引用的数据变量的数据类型保持一致;
& :特殊符号,表示所定义的数据类型变量为引用数据变量;
注意:
1) 在定义引用变量的时候,需要对引用变量进行初始值设置;
2) 对于引用变量,只能引用一个对象,不能引用多个对象。
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
int &c = a; /* 定义引用变量c,所引用的对象为变量a,此时a所谓的存储空间和c所访问的存储空间为相同内存空间 */
printf("&a = %p, &b = %p, &c = %p\n", &a, &b, &c);
printf("a = %d, b = %d, c = %d\n", a, b, c);
c = b; /* 赋值运算:将变量b的值赋值给变量c */
printf("a = %d, b = %d, c = %d\n", a, b, c);
}
4.2 数组的引用
#include <iostream>
using namespace std;
typedef int ARR[5];
int main()
{
int i;
int arr[5] = {1,2,3,4,5}; /* 定义有5个int类型元素的一维数组并初始化 */
/* 数组的引用需要整体引用:r_arr是一个引用数组类型变量,引用的数据类型变量是一个有5个int类型元素的一维数组arr */
int (&r_arr)[5] = arr; /* 等价于:ARR & r_arr = arr */
r_arr[3] = 333;
cout << arr <<" arr: ";
for (i = 0; i < 5; i++)
cout << arr[i] << " ";
cout << endl;
cout << r_arr <<" r_arr: ";
for (i = 0; i < 5; i++)
cout << r_arr[i] << " ";
cout << endl;
}
4.3 函数中的引用
1. 形参引用
在函数中,将函数的形参设置为引用类型变量,在函数调用的时候,根据所传递实参确定形参具体所引用对象。实质将实参引用传递给形参(实参取别名为形参),在函数内部访问形参也就访问实参存储空间(通过形参修改实参变量的值)。
#include <iostream>
using namespace std;
void swap(int a, int b)
{
cout << __LINE__ << ", a = " << a << ", b = " << b << endl;
int tmp = a;
a = b;
b = tmp;
cout << __LINE__ << ", a = " << a << ", b = " << b << endl;
}
/* 在函数定义的时候,将形参设置为引用变量,在函数调用的时候进行引用设置,所引用对象为传递的实参对象 */
void Swap(int &c, int &d)
{
cout << __LINE__ << ", c = " << c << ", d = " << d << endl;
int tmp = c;
c = d;
d = tmp;
cout << __LINE__ << ", c = " << c << ", d = " << d << endl;
}
int main()
{
int x = 5;
int y = 2;
cout << __LINE__ << ", x = " << x << ", y = " << y << endl;
swap(x, y);
cout << __LINE__ << ", x = " << x << ", y = " << y << endl;
Swap(x, y); /* 在函数调用的时候:c引用x,d引用y;在函数调用结束,返回实参修改结果 */
cout << __LINE__ << ", x = " << x << ", y = " << y << endl;
}
2. 函数返回值为引用
#include <iostream>
using namespace std;
int & test0(void)
{
static int a = 4; /* 定义static修饰的局部变量,作用域局部模块域,生命周期为程序执行到程序结束 */
cout << "&a :" << &a << endl;
return a;
}
int & test1(void)
{
int b = 5; /* auto局部变量,生命周期为模块域,不能作为返回引用对象 */
cout << "&b :" << &b << endl;
return b;
}
int main()
{
int &x = test0(); /* 在函数调用返回的时候,引用 */
cout << "x = " << x << endl;
cout << "&x :" << &x << endl;
#if 0
int &y = test1(); /* 在函数调用返回的时候,引用 */
cout << "y = " << y << endl;
cout << "&y :" << &y << endl;
#endif
}
4.4 指针的引用
所谓的指针的引用,指的是对指针变量取别名。
#include <stdio.h>
int main()
{
int a = 20;
int b = 10;
int *p = &a;
int * &q = p; /* 指针的引用,指针变量p取别名为q */
printf("&p = %p, &q = %p\n", &p, &q); /* p == q */
printf("a = %d, b = %d, *p = %d, *q = %d\n", a, b, *p, *q);
p = &b;
printf("a = %d, b = %d, *p = %d, *q = %d\n", a, b, *p, *q);
}
4.5 常量的引用
#include <iostream>
using namespace std;
int main()
{
// int &a = 100; /* 在定义引用变量的时候,不可以直接对常量进行引用 */
/* 在定义引用变量的时候,使用const关键字修饰,此时称为常引用,只能读访问,不能写访问 */
const int &b = 100; /* 常引用对象可以是常量数据值 */
int c = 5;
const int &d = c; /* 常引用对象可以变量 */
const int x = 3;
const int &y = x; /* 常引用对象可以是符号常量 */
}
4.6 引用和指针的关系
1. 在C语言、C++等其它语言中都有包含指针
指针的优点和必要性:
1) 指针可以有效表示数据结构;
2) 能够动态分配内存,实现内存的自由管理;
3) 能够方便的使用字符串,便捷高效地使用数组;
4) 指针直接域数据的存储地址有关。
2. 引用和指针的区别
指针式变量的地址,引用是给变量取别名(引用本质是通过指针实现)。
1) 程序为指针变量分配内存空间,而不为引用分配。
2) 指针使用地址解引用访问运算符(*)解引用访问所指向空间数据,而引用指针访问。
3) 指针在定义的时候,可以不设置指向,后续过程中设置,同时指向可以改变;而引用在定义的时候需要设置引用对象,且设置后不能改变引用对象;
4) 指针中有空指针NULL,而引用中没有空引用;引用的访问效率更高。
5) sizeof计算指针和引用空间大小,指针固定大小;而引用和引用对象数据类型有关;
6) 指针可以是多级指针没有限制,而引用只能是一级引用;
7) 自加(++)自减(--)运算,指针实现指向的偏移,引用实现引用对象数据结果的改变。
三, 函数重载和函数默认参数
1 函数重载
所谓的函数的重载,指的是在相同作用域下的多个函数名相同其参数不同的多个函数称为重载函数,整个行为称为函数重载。
重载函数具备的条件:
1) 作用域相同;
2) 函数名称相同;
3) 参数不同:
参数的数据类型不同、参数个数不同、参数顺序不同;
#include <stdio.h>
/* 重载函数:包含了4个 */
int add(int a, int b)
{
return a+b;
}
float add(float a, float b)
{
return a+b;
}
double add(double a, double b)
{
return a+b;
}
int add(int a, int b, int c)
{
return a+b+c;
}
int main()
{
/* 对于重载函数的访问:在程序编译过程中,根据所传递参数的数据类型决定所调用的函数 */
printf("%d\n", add(5, 6));
printf("%f\n", add(5.1, 6.3));
printf("%d\n", add(4, 5, 6));
}
注意:
1. C++中,函数重载是由编译器决定,
C语言使用的是gcc编译器,不支持函数重载;其实质在编译(语法检测并生成汇编代码)过程中,C语言函数的编译为汇编指令的过程中,直接使用函数名作为代码起始符号。
C++使用的是g++编译,支持函数重载;其实质在编译(语法检测并生成汇编代码)过程中,C++语言函数的编译为汇编指令的过程中,将函数名及其形参数据类型符号中的起始字母作为代码起始符。
2. 在函数的重载中,只与函数的形参有关,和函数的返回值无关。
3. 重载函数的调用,在程序编译过程中根据所传递参数的数据类型决定所调用的重载函数。
2 函数的默认参数值
所谓的函数默认参数值,指的是在函数声明过程中,给形参设置默认数据值;在函数调用的时候,如果有给形参传递实参值,则形参使用的是传递的实参数据值,如果没有给形参传递实参值,则形参使用所设置的默认参数值。
注意:
1. 函数默认参数值的设置,是在函数声明的时候进行设置。
2. 在设置默认参数值过程中,其中某一个参数设置了默认参数值,其该参数右边的所以参数均要设置默认参数值。
3. 设置默认参数值的函数使用:
1) (参数个数 - 已设置默认参数值个数) <= 所传递参数值个数 <= 参数个数
2) 所传递的实参从左向右依次顺序传递给形参。
#include <stdio.h>
void PrintVal(int a = 0, int b = 0, int c = 0)
{
printf("a = %d, b = %d, c = %d\n", a, b, c);
}
void print_val(int x, int y = 10, int z = 10);
int main()
{
PrintVal(3,4,5); /* 函数调用传递实参,形参使用传递的参数值 */
PrintVal(); /* 函数调用未传递实参,形参使用默认参数值 */
print_val(1, 2, 3); /* 所传递的实参从左向右依次顺序赋值给形参x,y,z */
print_val(1, 2);
print_val(1);
}
void print_val(int x, int y, int z)
{
printf("x = %d, y = %d, z = %d\n", x, y, z);
}
3 函数的占位参数
所谓的函数的占位参数,指的是函数定义的时候,函数的形参只有数据类型而没有形参变量名,此时的参数称为占位参数。
其占位参数值具备占位的意义,而不具备其它意义。主要用于在运算符重载过程中实现对于运算符++/--的前后运算之分。
占位参数可以设置默认参数值。
/* 在函数定义的时候,给函数设置占位参数,同时给占位参数设置默认参数值 */
void func(int a, int = 3)
{
printf("a = %d\n", a);
}
void func(int, double x)
{
printf("x = %f\n", x);
}
int main()
{
func(1, 4);
func(4);
func(2, 3.4);
}
4 函数重载和函数默认参数值同时使用
四, 内联函数
1 内联函数的定义
所谓的内联函数,类似于C语言中的预定义宏函数。具体的定义实现:
1) 非成员函数定义为内联函数
函数的声明和函数的定义为整体实现。并且在函数前使用关键字inline修饰。
/* 函数声明和定义为整体表示,且使用关键字inline进行修饰的函数称为内联函数 */
inline int func(int a)
{
return a++;
}
2) 成员函数定义为内联函数
一般表示类成员函数,如果定义为内联函数需要在类的内部完成成员函数的声明和定义。关键字inline可以省略也可以保留。
class Demo {
/* 类内部实现成员函数的定义和声明,关键inline可以省略,此时的成员函数自动为内联函数 */
int func(int a)
{
return a++;
}
}
2 内联函数编译
对于一个内联函数,并不是完全由关键字inline和成员函数所定义的位置决定,同时会由编译器共同决定。
内联是设计者给编译器的建议,只有满足编译内联规则的时候,才会将其生成为内联函数。同时编译器也会自动将未声明为内联的函数生成为内联函数
c++内联编译会有一些限制:
1) 不能存在任何形式的循环语句;
2) 不能存在过多的条件判断语句
3) 函数体不能过于庞大
4) 不能对函数进行取址操作
3 内联函数和普通函数的区别
内联函数相当于普通函数而言:
1) 占用更多的代码存储空间:
内联函数在编译过程中做函数代码的替换;
2) 执行效率更高
内联函数的优势省去函数调用时的压栈、跳转以及返回开销等,执行效率更高。
内联函数以空间换时间。
五, C++中的输入输出
1. 继承了C语言中的输入输出
直接使用C语言中标准C库所提供大的输入输出函数:printf、scanf、getchar、putchar等函数实现数据的输入和输出功能;
2. C++中新增了标准输入输出
a) 标准输出流(std::cout)
std::cout 作为标准输出流;
使用格式:
std::cout << 数据/* << 重定向访问运算符,将数据重定向到标准输出流中 */
注意:
1) 在有多个数据需要输出的时候,可以使用多个重定向访问运算符<<进行输出链接;
2) 数据可以是变量、常量;
3) std::endl 作为标准输出流的换行符,等价于C语言中"\n";
#include <iostream>
using namespace std;
int main()
{
int a = 3;
float b = 5.1;
char *str = "hello ikun";
int arr[5] = {1,2,3,4,5};
/* 有多个数据需要输出,则使用重定向访问运算符<<进行数据的链接输出 */
std::cout << "a = " << a << ", b = " << b << std::endl;
cout << "&a = " << &a << endl;
/* 输出变量为char指针变量(或者指针常量-实在就是字符串)的时候,输出的是指针所指向字符串数据
* 指针变量需要输出的是指针的地址值,可以将其转换为void类型指针输出
*/
cout << str << endl; /* 输出字符串数据,在遇到'\0'结束 */
cout << (void *)str << endl; /* 输出指针地址值 */
/* 需要输出的数组变量名,此时表示的是数组首元素地址:数组不能整体访问 */
cout << arr << endl;
}
b) 标准输入流(std::cin)
std::cin >> 数据变量名称/* 从标准输入流中读取数据赋值给变量 */
注意:如果需要输入的数据变量有多个,可以使用多个重定向访问运算符>>链接实现。
#include <iostream>
using namespace std;
int main()
{
int a = 3;
float b = 5.1;
char str[] = "hello ikun";
std::cin >> a; /* 从标准输入流读取数据赋值给变量a */
std::cin >> b; /* 从标准输入流读取数据赋值给变量b */
std::cout << "a = " << a << ", b = " << b << std::endl;
std::cin >> a >> b; /* 从标准输入流读取数据赋值给变量a和变量b */
std::cout << "a = " << a << ", b = " << b << std::endl;
std::cin >> str;
cout << str;
六,字符串类
在C++中字符串表示:
1. 继承C语言中字符串的特征
实质使用字符数组空间实现字符串数据的存储,以字符'\0'作为结束标识。
2. 在C++标准类库中新增string字符串类
可以实现是字符串数据的存储和运算,string类表示:
std::string 是标准C++类库中所提供的字符串数据类型;内部不仅实现了字符串数据的存储;
1) 提供了字符串数据运算符重载功能,直接实现基本运算实现字符串数据的运算。
+:实现两个字符串数据的连接;
=:实现字符串赋值运算,等价于C语言中函数strcpy功能;
+=:在原字符串数据后追加新字符串数据,等价于C语言中函数strcat功能;
比较运算符:>、>=、<、<=、==、!=
[]:数组元素访问运算符,访问字符串中指定下标元素,可以读写访问。
2) 提供其它访问接口
size():计算字符串中字符个数;
c_str():将C++的字符串数据转换为C语言指针形式表示;
七, C++内存管理
1 C语言中存管理方式
1. 静态分配:
全局变量:
存储静态存储区
生命周期是程序执行到程序结束
作用域(或者连接属性):未被static修饰的全局变量,其作用域为程序域;被static修饰的全局变量其作用域为文件域。
局部变量:
存储位置:未被static修饰的局部变量存储在栈区、被static修饰的局部变量存储在静态存储区;
生命周期:未被staitc修饰的局部变量生命周期为语句执行到模块结束;被static修饰的局部变量生命周期为语句执行到程序结束。
作用域:为局部作用域。
常量:存储在数据常量区
2. 动态分配
使用动态内存分配函数(malloc、calloc、realloc)按照使用者指定空间大小动态分配内存,同时在内存使用结束的时候使用函数(free)进行动态释放。
存储位置:堆区
生命周期:由使用者跟进实际需求动态分配和释放决定;
#include <stdlib.h>
void *malloc(size_t size);
void free(void *ptr);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
2 C++内存管理
1. C++继承C语言所有内存管理方式
2. 同时引入新的内存管理方式
在动态内存管理的基础上,新增使用运算符new和delete动态管理内存方式
2.1 运算符new和delete的使用
1. 运算符new动态分配内存空间
动态开辟单个数据元素存储空间,实质就是分配数据类型变量存储空间:
数据类型 * 指针变量名 = new 数据类型;
动态开辟连续多个相同数据类型元素存储空间,实质就是分配数组元素存储空间:
数据类型 指针变量名 = new 数据类型[常量表达式];/ 常量表达式表示所分配数组元素的个数 */
2. 运算符delete动态释放内存空间
单个数据元素空间的释放
delete 指针变量名;
连续多个数据元素存储空间的释放
delete []指针变量名;
#include <iostream>
using namespace std;
int main()
{
int *p = new int; /* 开辟1个int数据类型元素存储空间 */
*p = 5;
std::cout << *p << std::endl;
delete p; /* 释放p指向存储空间 */
p = NULL;
int *q = new int [5]; /* 开辟连续存储空间:可以存储5个int类型数据元素 */
for (int i = 0; i < 5; i++)
*(q+i) = i;
for (int i = 0; i < 5; i++)
cout << "q[" << i<<"] = " << q[i] << endl;
delete []q; /* 释放q指向连续存储空间 */
}
2.2 new和malloc区别
在C++中,new和malloc用于动态内存管理
1. 属性不同
new和delete,是C++中的运算符关键字,由编译器所支持;
运算符new和delete可以重载;
malloc和free,是库函数,在使用的时候需要添加头文件和库支持。
无法对其进行重载
2. 使用方式不同
malloc在动态开辟空间的时候需要显示填入所申请内存空间的大小;
new在动态开辟空间的时候不需要显示填入内存空间的大小,会根据new的数据类型自动分配;
int *malloc_p = (int *)malloc(4);
free(malloc_p));
int *new_p = new int;
delete new_p;
3. 存储内存位置不同
malloc所申请的内存一定是堆区存储空间;
new所申请的内存空间为自由存储空间;
C++内存区间分为:堆区、栈区、自由存储区、全局静态存储区、常量存储区
自由存储区是C++中动态内存分配和释放对象的一个概念,通过new分配的内存区域称为自由存储区,需要通过delete释放内存;自由存储区可以是堆区、栈区、全局静态存储区,主要是由new的实现和C++编译器默认new申请内存决定,大多数情况下new默认使用堆区实现自由存储区。
4. 返回类型不同
malloc返回值为void类型指针,需要根据使用进行类型强制转换;
new返回值为所分配数据类型指针,无需强制转换直接使用。
5. 分配失败情况不同
malloc失败返回NULL,可以判断使用;
new失败会抛出bac_alloc异常;
6. 内存大小扩张
malloc可以使用realloc扩张所分配的内存;
new没有扩张内存机制。
7. 定义对象系统调度过程不同
使用new操作符来分配对象内存时会经历三个步骤:
a) 调用operator new 函数(对于数组是operator new[])分配一块足够的内存空间(通常底层默认使用malloc实现,除非程序员重载new符号)以便存储特定类型的对象;
b) 编译器运行相应的构造函数以构造对象,并为其传入初值。
c) 对象构造完成后,返回一个指向该对象的指针。
使用delete操作符来释放对象内存时会经历两个步骤:
d) 调用对象的析构函数。
e) 编译器调用operator delete(或operator delete[])函数释放内存空间(通常底层默认使用free实现,除非程序员重载delete符号)。