这套C++教程能够很好的帮助你入门,让你掌握C++基础并且打开通向高级C++工程师的大门,通俗易懂深入浅出是这套教程最大的特点,让你能够很轻松地学习C++。
可以关注微 信 公 众 号 “C和C加加” 回复“ZXC”领取详细教程和更多进阶视频教程大全
标识符是用来标识变量、函数、类、模块,或任何其他用户自定义项目的名称。一个标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)。
标识符内不允许出现标点字符,比如 @、& 和 %。C++ 是区分大小写的编程语言。
七种基本的C++数据类型:bool、char、int、float、double、void、wchar_t
类型修饰符:signed、unsigned、short、long
注:一些基本类型可以使用一个或多个类型修饰符进行修饰,比如:signed short int简写为short、signed long int 简写为long。
这个与机器、操作系统、编译器有关。比如同样是在32bits的操作系统系,VC++的编译器下int类型为占4个字节;而tuborC下则是2个字节。
原因:
c/c++规定int字长和机器字长相同
操作系统字长和机器字长未必一致
编译器根据操作系统字长来定义int字长
C++中的一种派生数据类型,它是由用户定义的若干枚举常量的集合;枚举元素是一个整型,枚举型可以隐式的转换为int型,int型不能隐式的转换为枚举型。
如果枚举没有初始化, 即省掉"=整型常数"时, 则从第一个标识符开始;
默认情况下,第一个名称的值为 0,第二个名称的值为 1,第三个名称的值为 2,以此类推。但是,您也可以给名称赋予一个特殊的值,只需要添加一个初始值即可。
例如:
(1)整型常量
整型常量可以分为有符号整型常量和无符号整型常量
八进制:以0开头,即以0作为八进制数的前缀,每位取值范围是0~7,八进制数通常是无符号数。
例如:016、0101、0128都是合法的八进制数
十六进制:以0x或者0X开头,其数码取值范围0 ~ 9,以及A ~ F或者a ~ f
例如:0X2A1、0XC5、0XFFFF都是合法的16进制数
(2)实型常量
小数表示法:由整数部分和小数部分组成,整数部分和小数部分每位取值范围是0~9,例如:0.0、3.25、0.00596、2.0
指数表示法:指数部分以符号"e"或者"E"开始,但必须是整数,并且符号"e"或"E"两边都必须有一个数,例如:1.2e20和-3.4e-2
(3)字符常量
字符常量是单引号括起来的字符,例如:'a'和'?'都是合法字符常量。字符'a'的ASCII码值是97,字符'A'的ASCII码值是41,字符'?'的ASCII码值是63
转义字符是特殊的字符常量,使用时以字符串’'代表开始转义,和后面不同的字符表示转义的字符。转义字符表如下所示:
(4)字符串常量
是由一对双引号括起来的零个或多个字符序列,例如:“welcome to our school”、“hello world”。""可以表示一个空字符串。
字符常量’A’和字符串常量"A"是不同的,字符串常量"A"是由'A'和'\0'两个字符组成的,字符串长度是2,字符串常量'A'只是一个字符,没有长度。
(5)其他常量
变量其实只不过是程序可操作的存储区的名称。C++ 中每个变量都有指定的类型,类型决定了变量存储的大小和布局,该范围内的值都可以存储在内存中,运算符可应用于变量上。
(1)标识符:是用来对C++程序中的常量、变量、语句标号以及用户自定义函数的名称进行标识的符号。
标识符命名规则:
c++保留关键字,如图所示:
(2)变量与变量说明:变量使用前一定要定义或说明,变量声明的一般形式如下:[修饰符] 类型 变量名标识符
;
类型是变量类型的说明符,说明变量的数据类型。修改师傅是任选的,可以没有。
(3)整型变量:整型变量可以分为短整型、整型和长整型,变量类型说明符分别是short、int、long。根据是否有符号还可分为以下6种。
(4)实型变量:又称为浮点型变量,变量可分为单精度(float)、双精度(double)和长双精度(long double)三种。
(5)变量赋值:变量值是动态改变的,每次改变都需要进行赋值运算。变量赋值的形式如下:变量名标识符 = 表达式
,例如:
(6)变量赋初值:可以在声明变量的时候就把数据赋给变量,这个过程叫变量赋初值,赋初值的情况有以下几种:
(7)字符变量:
(8)变量的作用域
局部变量:在函数或一个代码块内部声明的变量,称为局部变量。它们只能被函数内部或者代码块内部的语句使用。
全局变量:在所有函数外部定义的变量(通常是在程序的头部),称为全局变量。全局变量的值在程序的整个生命周期内都是有效的。
局部变量和全局变量的名称可以相同,但是在函数内,局部变量的值会覆盖全局变量的值。
当局部变量被定义时,系统不会对其初始化;定义全局变量时,系统会自动初始化值:int float double 0,char ’\0‘,指针 NULL
(1)结构体
结构体可以包含不同数据类型的结构。
定义结构体的一般形式
结构体变量名的定义和初始化:
//定义结构体同时声明结构体变量名
struct 结构体类型名
{
成员类型1 成员名1;
成员类型2 成员名2;
... ...
成员类型n 成员名n;
}变量名1,变量名2,...变量名n;
//先定义结构体
[struct] 结构体类型名 变量名;
//直接定义
struct
{
成员类型1 成员名1;
成员类型2 成员名2;
... ...
成员类型n 成员名n;
}变量名1,变量名2,...变量名n;
struct person
{
int year;
int age;
string name;
}p1 = {2019,24,"heiren"}, p1 = { 2020,24,"heiren" };
struct person
{
int year;
int age;
string name;
};
struct person p1 = { 2019,24,"heiren" }, p1 = { 2020,24,"heiren" };
struct
{
int year;
int age;
string name;
}p1 = {2019,24,"heiren"}, p1 = { 2020,24,"heiren" };
结构体变量的使用:
结构体变量名.成员名
指针变量名->成员名;
struct person
{
int year;
int age;
string name;
}p[2] ={ {2019,24,"heiren"}, { 2020,24,"heiren" }};//可以不指定数组元素个数
p[1].age;
结构体作为函数传递有三种:值传递,引用传递,指针传递
结构体大小和字节对齐
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐.
为什么需要字节对齐?各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。
三个个概念:
自身对齐值:数据类型本身的对齐值,结构体或类的的自身对齐值是其成员中最大的那个值,例如char类型的自身对齐值是1,short类型是2;
指定对齐值:编译器或程序员指定的对齐值,32位单片机的指定对齐值默认是4;
有效对齐值:自身对齐值和指定对齐值中较小的那个。
字节对齐的三个准则
结构体变量的首地址能够被其有效对齐值的大小所整除
结构体的总大小为结构体有效对齐值的整数倍。
结构体每个成员相对于结构体首地址的偏移量都是有效对齐值的整数倍。
可以通过#pragma pack(n)来设定变量以n字节对齐方式
(2)公用体(union)
几个不同的变量共享同一个地址开始的内存空间。
成员类型可以是基本数据类型,也可以是构造数据类型。
公用体变量初始化时,只能对第一个成员赋值。
公用体变量所占的内存长度等于最长的成员长度。
公用体变量在一个时刻只能一个成员发挥作用,赋值时,成员之间会互相覆盖,最后一次被赋值的成员起作用。
定义:
union 共同体类型名
{
成员类型1 成员名1;
成员类型2 成员名2;
... ...
成员类型n 成员名n;
};
初始化
union data
{
int i;
float f;
char c;
}x = {123};
union data
{
float f;
int i;
char c;
};
data x = {12.3};
union
{
char c;
int i;
float f;
}x = {’y‘};
引用
共同体变量名.成员名;
union data
{
int i;
float f;
char c;
}x = {12};
int main()
{
cout << x.i << " " << x.f << " " << x.c << endl;//12 1.68156e-44
x.c = 'c';
cout << x.i <<" "<< x.f << " " << x.c << endl;//99 1.38729e-43 c
return 0;
}
(1)控制台屏幕
(2)操作控制
在头文件iomanip.h中定义了一些控制流输出格式的函数,默认情况下整型数按十进制形式输出,也可以通过hex将其设置为十六进制输出。流操作的控制具体函数如下
(1)算术运算符:算术运算主要指常用的加(+)、减(-)、乘(*)、除(/)四则运算,算术运算符中有单目运算符和双目运算符。
(2)关系运算符
关系运算符主要是对两个对象进行比较,运算结果是逻辑常量真或假。
(3)逻辑运算符
逻辑运算符是对真和假这两种逻辑值进行运算,运算后的结果仍是一个逻辑值
(4)赋值运算符
(5)位运算符
(6)移位运算符
(7)sizeof运算符
(8)条件运算符
(9)逗号运算符
(10)运算符优先级
(11) 运算符重载
所谓重载,就是赋予新的含义。函数重载(Function Overloading)可以让一个函数名有多种功能,在不同情况下进行不同的操作。运算符重载(Operator Overloading)也是一个道理,同一个运算符可以有不同的功能。
运算符重载是通过函数实现的,它本质上是函数重载。
允许重载的运算符
不允许重载的运算符
重载运算符遵循的规则:
一般格式:
函数类型 operator运算符(参数列表)
{
函数体
}
//举个栗子:定义一个向量类,通过运算符重载,可以用+进行运算。
class Vector3
{
public:
Vector3();
Vector3(double x,double y,double z);
public:
Vector3 operator+(const Vector3 &A)const;
void display()const;
private:
double m_x;
double m_y;
double m_z;
};
Vector3::Vector3() :m_x(0.0), m_y(0.0), m_z(0.0) {}
Vector3::Vector3(double x, double y,double z) : m_x(x), m_y(y), m_z(z) {}
//运算符重载
Vector3 Vector3::operator+(const Vector3 &A) const
{
Vector3 B;
B.m_x = this->m_x + A.m_x;
B.m_y = this->m_y + A.m_y;
B.m_z = this->m_z + A.m_z;
return B;
}
void Vector3::display()const
{
cout<<"(" << m_x << "," << m_y << "," << m_z << ")" << endl;
}
运算符重载的形式有两种:重载函数作为类的成员,重载函数作为类的友元函数
根据运算符操作数的不同:双目运算符作为类成员函数,单目运算符作为类的成员函数,双目运算符作为类的友员函数,单目运算符作为类的友元函数。
双目运算符作为友元函数时需要制定两个参数。
运算符重载函数作为类成员函数可以显式调用。
常用运算符的重载
(1)算数表达式
(2)关系表达式
(3)条件表达式
(4)赋值表达式
(5)逻辑表达式
(6) 逗号表达式
可以关注微信公众号 “C和C加加” 回复“ZXC”有更多详细教程领取!
while(conditon)//0为false,非0为true
{
statement(s);
}
for(init;conditon;increment)//0为false,非0或什么也不写为true
{
statement(s);
}
int array[5] = { 11, 22, 33, 44, 55 };
for (int x : array)
{
cout << x << " ";
}
cout << endl;
// auto 类型也是 C++11 新标准中的,用来自动获取变量的类型
for (auto x : array)
{
cout << x << " ";
}
int a[4] = { 4,3,2,1 };
for each (int var in a)
{
cout << var << " ";
}
if(expr)
{
statement;//如果expr为true将执行的语句块
}
if(expr)
{
statement1;// 如果expr为true将执行的语句块
}
else
{
statement2;// 如果expr为false将执行的语句
}
if(expr1)
{
statement1;// 如果expr1为true将执行的语句块
}
elseif(expr2)
{
statement2;// 如果expr2为true将执行的语句块
}
...
else
{
statementElse;// 当上面的表达式都为false执行的语句块
}
switch(expression){
case constant-expression :
statement(s);
break;
case constant-expression :
statement(s);
break;
// 您可以有任意数量的 case 语句
default : // 可选的
statement(s);
}
每个case后满的常量表达式必须各不相同。
case语句和default语句出现的顺序对执行结果没有影响。
若case后没有break,执行完就不会判断,继续执行下一个case语句。直到遇到brerak。
default后面如果没有case,则break可以省略
多个case可以用一组执行语句
char c = 'A';
switch (c)
{
case 'A':
case 'B':
case 'C':
cout << "及格了" << endl;
break;
default:
cout << "不及格" << endl;
}
//如果 Exp1 为真,则计算 Exp2 的值,结果即为整个 ? 表达式的值。如果 Exp1 为假,则计算 Exp3 的值,结果即为整个 ? 表达式的值
Exp1 ? Exp2 : Exp3;
预处理程序(删除程序注释,执行预处理命令等)–>编译器编译源程序
#define 标识符 字符串
#include 或者#include“filename”
//如果标识符被#define定义过,执行程序段1,否则执行程序段2
#ifdef 标识符
程序段1
#else
程序段2
#endif
//如果标识符没有被#define定义过,执行程序段1,否则执行程序段2
#ifndef 标识符
程序段1
#else
程序段2
#endif
//如果表达式为true,执行程序段1,否则执行程序段2
#if 表达式
程序段1
#else
程序段2
#endif
初始化的形式:数据类型 数组名[常量表达式] = {初值表};
为数组的某一个元素赋值:数组名[下标] =值
(下标从0开始)
数组的引用:数组名[下标]
定义一维数组的形式:数据类型 数据名[常量表达式1][常量表达式2]
初始化的形式:数据类型 数组名[常量表达式1] [常量表达式2]= {初值表};
为数组的某一个元素赋值:数组名[行下标][列下标] =值(下标从0开始)
数组的引用:数组名[行下标][列下标]
将所有数据写在一个花括号内,自动按照数组元素个数在内存中排列的顺序赋值
可对部分元素赋值,其余元素的值自动取0.
定义初始化数组时,可以省略第一维的长度,第二维不能省,系统会自动确认行数
char类型的数组,在字符数组中最后一位为’\0’)时,可以看成时字符串。在C++中定义了string类,在Visual C++中定义了Cstring类。
字符串中每一个字符占用一个字节,再加上最后一个空字符。
指针是一个变量,其值为另一个变量的地址。即内存位置的直接地址。
声明的一般形式:
数据类型 *指针变量名;
int *ip; //整型的指针
double *dp; //double 型的指针
float *fp; //浮点型的指针
char *ch; //字符型的指针
指针变量的初始化:
数据类型 *指针变量名 = &变量名;
*指针变量名 = &变量名;
int a;
int *p = &a;
int *p2;
p2 = &a;
指针变量的引用:
int x = 3;
int y;
int *p;
p = &x;
y = *p;//y = a
指针运算(地址运算)
算术运算(移动指针运算):加减,自增自减。
p+n运算得到的地址是p+n*sizeof(数据类型)。
两个相同数据类型的指针可以进行加减运算,一般用于数组的操作中。
关系运算:指针指向同一串连续存储单元才有意义,比如数组。与0比较,判断是不是空指针。
赋值运算:变量地址赋值给指针变量,数组元素地址赋值给指针变量,指针变量赋值给其他指针变量。
int arr[10],len;
int *p1 = &arr[2],*p2 = &arr[5];
len = p2-p1;//arr[2] 和arr[5]之间的元素个数 3
new和delete运算符
指针变量 = new 数据类型(初值);
delete 指针变量;
delete[] 指针变量;//释放为多个变量分配的地址
int *ip;
ip= new int(1);
delete ip;
int *ip;
ip= new int[10];
for (int i = 0; i < 10;i++)
{
ip[i] = i;
}
delete[] ip;
int a[3][4] = {0};
一维数组
int* arr1 = new int[2];//delete []arr1;
int* arr2 = new int[3]{ 1,2 };//delete []arr2
二维数组
int m=2, n=3;
int** arr3 = new int*[2];//delete []arr3
for (int i = 0; i < 10; ++i)
{
arr3[i] = new int[3]; // delete []arr3[i]
}
int* arr4 = new int[m*n];//数据按行存储 delete []arr3
char ch[] = "heiren";char *p = ch;
char *p = "heiren";
char * p;p = "Heiren";
引用可以看做是数据的一个别名,通过这个别名和原来的名字都能够找到这份数据,类似于window中的快捷方式。
引用不占内存空间,必须在定义的同时初始化,且不能再引用其他数据。
引用在定义时需要添加&,在使用时不能添加&,使用时添加&表示取地址
引用型变量声明:数据类型 &引用名 = 变量名;
int a;
int &b = a;//a和b表示相同的变量,具有相同的地址。
引用可以作为函数参数,也可以作为函数返回值。
void swap(int &r1, int &r2) {
int temp = r1;
r1 = r2;
r2 = temp;
}
int &add1(int &r) {
r += 1;
return r;
}
int main()
{
int a = 12;
int b = add1(a);
cout << a << " "<
将引用作为函数返回值时不能返回局部数据的引用,因为当函数调用完成后局部数据就会被销毁。
函数在栈上运行,函数掉用完,后面的函数调用会覆盖之前函数的局部数据。
int &add1(int &r) {
r += 1;
int res = r;
return res;
}
void test()
{
int xx = 123;
int yy = 66;
}
int main()
{
int a = 12;
int &b = add1(a);
int &c = add1(a);
test();//函数调用,覆盖之前函数的局部数据
cout << a << " "<
后两条总结一下就是:调用函数前,程序得知道有这个函数,声明就是提前让程序知道有这么的玩意
函数声明:
函数类型 函数名(参数列表);
eg:
int max(int a,int b);//声明函数时,a,b可以省略
int max(int,int);
void show();
函数定义:
函数类型 函数名(参数列表)
{
函数体;
}
eg:
int max(int a,int b)
{
int z;
z = a>b?a:b;
return z;
}
形参和实参必须个数相同、类型一致,顺序一致,函数传递方式:传值,指针,引用
函数名(实参列表);
show();
函数的嵌套调用:
int a()
{
return 666;
}
int b(int sum)
{
return sum+a()
}
int main()
{
cout<
函数的递归调用:直接递归调用和间接递归调用
//直接递归调用:求1+...n的值
int total(int sum)
{
if (sum == 1)
{
return 1;
}
return sum + total(sum - 1);
}
int main()
{
cout << "total = " << total(10) << endl;//total = 55
system("pause");
return 0;
}
//间接递归调用
int f2();
int f1()
{
...
f2()
}
int f2()
{
f1();
}
//定义
string 变量;
string str1;
//赋值
string str2 = "ShangHai";
string str3 = str2;
str3[3] = '2';//对某个字符赋值
//字符串数组
string 数组名[常量表达式]
string arr[3];
#include
#include
#include
string str;//生成空字符串
string s(str);//生成字符串为str的复制品
string s(str, strbegin,strlen);//将字符串str中从下标strbegin开始、长度为strlen的部分作为字符串初值
string s(cstr, char_len);//以C_string类型cstr的前char_len个字符串作为字符串s的初值
string s(num ,c);//生成num个c字符的字符串
string s(str, stridx);//将字符串str中从下标stridx开始到字符串结束的位置作为字符串初值
size()和length();//返回string对象的字符个数
max_size();//返回string对象最多包含的字符数,超出会抛出length_error异常
capacity();//重新分配内存之前,string对象能包含的最大字符数
>,>=,<,<=,==,!=//支持string与C-string的比较(如 str<”hello”)。 使用>,>=,<,<=这些操作符的时候是根据“当前字符特性”将字符按字典顺序进行逐一得 比较,string (“aaaa”)
面向对象概述
类也是一种数据类型。
类的声明:
class 类名
{
public:
公有数据成员;
公有成员函数;
private:
私有数据成员;
私有成员函数;
protected:
保护数据成员;
保护成员函数;
};
成员函数的定义:类内,类外,类外内联函数
//类外
返回类型 类名:成员函数名(参数列表)
{
函数体;
}
//内联函数:类外
inline 返回类型 类名:成员函数名(参数列表)
{
函数体;
}
C++中public、private、protected只能修饰类的成员,不能修饰类,C++中的类没有共有私有之分
类内部没有访问权限的限制,都可以互相访问。
在C++中用class定义的类中,其成员的默认存取权限是private。
//1.声明类同时定义对象
class 类名
{
类体;
}对象名列表;
//2.先声明类,再定义对象
类名 对象名(参数列表);//参数列表为空时,()可以不写
//3. 不出现类名,直接定义对象
class
{
类体;
}对象名列表;
//4.在堆上创建对象
Person p(123, "yar");//在栈上创建对象
Person *pp = new Person(234,"yar");//在堆上创建对象
注:不可以在定义类的同时对其数据成员进行初始化,因为类不是一个实体,不合法但是能编译运行
对象成员的引用:对象名.数据成员名 或者 对象名.成员函数名(参数列表)
构造函数名必须与类名相同
没有任何返回值和返回类型
创建对象自动调用,不需要用户来调用,且只掉用一次
类没有定义任何构造函数,编译系统会自动为这个类生成一个默认的无参构造函数
构造函数定义
//1.类中定义 2.类中声明,类外定义
[类名::]构造函数名(参数列表)
{
函数体
}
创建对象
类名 对象名(参数列表);//参数列表为空时,()可以不写
带默认参数的构造函数
class Person
{
public:
Person(int = 0,string = "张三");
void show();
private:
int age;
string name;
};
Person::Person(int a, string s)
{
cout<
带参数初始化表的构造函数
类名::构造函数名(参数列表):参数初始化表
{
函数体;
}
参数初始化列表的一般形式:
参数名1(初值1),参数名2(初值2),...,参数名n(初值n)
class Person
{
public:
Person(int = 0,string = "张三");
void show();
private:
int age;
string name;
};
Person::Person(int a, string s):age(a),name(s)
{
cout << a << " " << s << endl;
}
构造函数重载:构造函数名字相同,参数个数和参数类型不一样。
拷贝构造函数
类名::类名(类名&对象名)
{
函数体;
}
class Person
{
public:
Person(Person &p);//声明拷贝构造函数
Person(int = 0,string = "张三");
void show();
private:
int age;
string name;
};
Person::Person(Person &p)//定义拷贝构造函数
{
cout << "拷贝构造函数" << endl;
age = 0;
name = "ABC";
}
Person::Person(int a, string s):age(a),name(s)
{
cout << a << " " << s << endl;
}
int main()
{
Person p(123, "yar");
Person p2(p);
p2.show();
return 0;
}
//输出
123 yar
拷贝构造函数
age=0
name=ABC
对象指针的声明和使用
类名 *对象指针名;
对象指针 = &对象名;
//访问对象成员
对象指针->数据成员名
对象指针->成员函数名(参数列表)
Person p(123, "yar");
Person* pp = &p;
Person* pp2 = new Person(234,"yar")
pp->show();
指向对象成员的指针
数据成员类型 *指针变量名 = &对象名.数据成员名;
函数类型 (类名::*指针变量名)(参数列表);
指针变量名=&类名::成员函数名;
(对象名.*指针变量名)(参数列表);
Person p(123, "yar");
void(Person::*pfun)();
pfun = &Person::show;
(p.*pfun)();
this指针
每个成员函数都有一个特殊的指针this,它始终指向当前被调用的成员函数操作的对象
class Person
{
public:
Person(int = 0,string = "张三");
void show();
private:
int age;
string name;
};
Person::Person(int a, string s):age(a),name(s)
{
cout << a << " " << s << endl;
}
void Person::show()
{
cout << "age="<age << endl;
cout << "name=" <name << endl;
}
以关键字static开头的成员为静态成员,多个类共享。
//类内声明,类外定义
class xxx
{
static 数据类型 静态数据成员名;
}
数据类型 类名::静态数据成员名=初值
//访问
类名::静态数据成员名;
对象名.静态数据成员名;
对象指针名->静态数据成员名;
静态成员函数
借助友元(friend),可以使得其他类中得成员函数以及全局范围内得函数访问当前类得private成员。
友元函数
友元函数不是类的成员函数,所以没有this指针,必须通过参数传递对象。
友元函数中不能直接引用对象成员的名字,只能通过形参传递进来的对象或对象指针来引用该对象的成员。
//1.将非成员函数声明为友元函数
class Person
{
public:
Person(int = 0,string = "张三");
friend void show(Person *pper);//将show声明为友元函数
private:
int age;
string name;
};
Person::Person(int a, string s):age(a),name(s)
{
cout << a << " " << s << endl;
}
void show(Person *pper)
{
cout << "age="<< pper->age << endl;
cout << "name=" << pper->name << endl;
}
int main()
{;
Person *pp = new Person(234,"yar");
show(pp);
system("pause");
return 0;
}
//2.将其他类的成员函数声明为友元函数
//person中的成员函数可以访问MobilePhone中的私有成员变量
class MobilePhone;//提前声明
//声明Person类
class Person
{
public:
Person(int = 0,string = "张三");
void show(MobilePhone *mp);
private:
int age;
string name;
};
//声明MobilePhone类
class MobilePhone
{
public:
MobilePhone();
friend void Person::show(MobilePhone *mp);
private:
int year;
int memory;
string name;
};
MobilePhone::MobilePhone()
{
year = 1;
memory = 4;
name = "iphone 6s";
}
Person::Person(int a, string s):age(a),name(s)
{
cout << a << " " << s << endl;
}
void Person::show(MobilePhone *mp)
{
cout << mp->year << "年 " << mp->memory << "G " << mp->name << endl;
}
int main()
{
Person *pp = new Person(234,"yar");
MobilePhone *mp = new MobilePhone;
pp->show(mp);
system("pause");
return 0;
}
友元类
当一个类为另一个类的友元时,称这个类为友元类。 友元类的所有成员函数都是另一个类中的友元成员。
语法形式:friend [class] 友元类名
继承就是再一个已有类的基础上建立一个新类,已有的类称基类或父类,新建立的类称为派生类和子类;派生和继承是一个概念,角度不同而已,继承是儿子继承父亲的产业,派生是父亲把产业传承给儿子。
一个基类可以派生出多个派生类,一个派生类可以继承多个基类
派生类的声明:
继承方式:
public-基类的public成员和protected成员的访问属性保持不变,私有成员不可见。
private-基类的public成员和protected成员成为private成员,只能被派生类的成员函数直接访问,私有成员不可见。
protected-基类的public成员和protected成员成为protected成员,只能被派生类的成员函数直接访问,私有成员不可见。
利用using关键字可以改变基类成员再派生类中的访问权限;using只能修改基类中public和protected成员的访问权限。
class Base
{
public:
void show();
protected:
int aa;
double dd;
};
void Base::show(){
}
class Person:public Base
{
public:
using Base::aa;//将基类的protected成员变成public
using Base::dd;//将基类的protected成员变成public
private:
using Base::show;//将基类的public成员变成private
string name;
};
int main()
{
Person *p = new Person();
p->aa = 12;
p->dd = 12.3;
p->show();//出错
delete p;
return 0;
}
派生类的构造函数和析构函数
派生类名(总参数列表):基类名(基类参数列表),子对象名1(参数列表){构造函数体;}
class Base
{
public:
Base(int, double);
~Base();
private:
int aa;
double dd;
};
Base::Base(int a, double d) :aa(a), dd(d)
{
cout << "Base Class 构造函数!!!" << endl;
}
Base::~Base()
{
cout << "Base Class 析构函数!!!" << endl;
}
class Person:public Base
{
public:
Person(int,double,string);
~Person();
private:
string name;
};
Person::Person(int a,double d,string str):Base(a,d),name(str)
{
cout << "Person Class 构造函数!!!" << endl;
}
Person::~Person()
{
cout << "Person Class 析构函数!!!" << endl;
}
int main()
{
cout << "创建Person对象..." << endl;
Person *p = new Person(1,2,"yar");
cout << "删除Person对象...." << endl;
delete p;
system("pause");
return 0;
}
一个派生类同时继承多个基类的行为。
多继承容易让代码逻辑复杂、思路混乱,一直备受争议,中小型项目中较少使用,后来的 Java、C#、PHP 等干脆取消了多继承。
多重继承派生类声明的一般形式:
多重继承派生类的构造函数:
类名::同名成员名;
c++引入虚基类使得派生类再继承间接共同基类时只保留一份同名成员。
虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class)。
派生类的 同名成员 比虚基类的 优先级更高
虚基类的声明:class 派生类名:virtual 继承方式 基类名
class A//虚基类
{
protected:
int a;
};
class B: virtual public A
{
protected:
int b;
};
class C:virtual public A
{
protected:
int c;
};
class D:public B,public C
{
protected:
int d;
void show()
{
b = 123;
c = 23;
a = 1;
}
};
如果 B 或 C 其中的一个类定义了a,也不会有二义性,派生类的a 比虚基类的a 优先级更高。
如果 B 和 C 中都定义了 a,那么D直接访问a 将产生二义性问题。
应用:c++中的iostream , istream , ostream,base_io
数据类型的转换,编译器会将小数部分直接丢掉(不是四舍五入)
只能将将派生类赋值给基类(C++中称为向上转型): 派生类对象赋值给基类对象、将派生类指针赋值给基类指针、将派生类引用赋值给基类引用
派生类对象赋值给基类对象,舍弃派生类新增的成员;派生类指针赋值给基类指针,没有拷贝对象的成员,也没有修改对象本身的数据,仅仅是改变了指针的指向;派生类引用赋值给基类引用,和指针的一样。、
上转型后通过基类的对象、指针、引用只能访问从基类继承过去的成员(包括成员变量和成员函数),不能访问派生类新增的成员
不同的对象可以使用同一个函数名调用不同内容的函数。
在基类中不执行具体的操作,只为派生类提供统一结构的虚函数,将其声明为虚函数。
实现程序多态性的一个重要手段,使用基类对象指针访问派生类对象的同名函数。
virtual 函数类型 函数名 (参数列表);
class A
{
public:
virtual void show()
{
cout << "A show" << endl;
}
};
class B: public A
{
public:
void show()
{
cout << "B show" << endl;
}
};
int main()
{
B b;
b.show();//B show
A *pA = &b;
pA->show();//B show 如果show方法前没用virtual声明为虚函数,这里会输出A show
system("pause");
return 0;
}
输入流-从输入设备流向内存的流。
输出流-从内存流出设备的流。
内存缓冲区-用来存放流中的数据。
输入输出流程:键盘输入=》键盘缓冲区=(回车触发)》程序的输入缓冲区=》‘>>’提取数据
输出缓冲区=(缓冲满或endl)》‘<<’送到 显示器显示
输入/输出流类:
iostream:ios ,istream,ostream,iostream
fstream:ifstream,ofstream,fstream
strstream:istrstream,ostrstream,strstream
C++的输入/输出流库(iostream)中定义了4个标准流对象:cin(标准输入流-键盘),cout(标准输出流-屏幕),cerr(标准错误流-屏幕),clog(标准错误流-屏幕)
cerr 不使用缓冲区,直接向显示器输出信息;而输出到 clog 中的信息会先被存放到缓冲区,缓冲区满或者刷新时才输出到屏幕。
cout 是 ostream 类的对象,ostream 类的无参构造函数和复制构造函数都是私有的,所以无法定义 ostream 类的对象。
使用>>提取数据时,系统会跳过空格,制表符,换行符等空白字符。所以一组变量输入值时,可用这些隔开。
输入字符串,也是跳过空白字符,会在串尾加上字符串结束标志\0。
int x;
double y;
cin>>x>>y;
//输入 22 66.0 两个数之间可以用空格、制表符和回车分隔数据
char str[10];
cin>>str;//hei ren 字符串中只有hei\0
输入流中的成员函数
输出流对象
格式化输出
iomanip 中定义的流操作算子:
流操作算子使用方法:cout << hex << 12 << "," << 24;//c,18
setiosflags() 算子
setiosflags() 算子实际上是一个库函数,它以一些标志作为参数,这些标志可以是在 iostream 头文件中定义的以下几种取值,它们的含义和同名算子一样。
如果两个相互矛盾的标志同时被设置,结果可能就是两个标志都不起作用,应该用 resetiosflags 清除原先的标志
ostream 类中的成员函数:
setf 和 unsetf 函数用到的flag,与 setiosflags 和 resetiosflags 用到的完全相同。
文件-指存储在外部介质上的数据集合,文件按照数据的组织形式不一样,分为两种:ASCII文件(文本/字符),二进制文件(内部格式/字节)
ASCII文件输出还是二进制文件,数据形式一样,对于数值数据,输出不同
C++ 标准类库中有三个类可以用于文件操作,它们统称为文件流类。这三个类是:
ifstream:输入流类,用于从文件中读取数据。
ofstream:输出流类,用于向文件中写人数据。
fstream:输入/输出流类,既可用于从文件中读取数据,又可用于 向文件中写人数据。
文件流对象定义:
open函数:void open(const char* szFileName, int mode);
ios::binary 可以和其他模式标记组合使用
流类的构造函数
eg:ifstream::ifstream (const char* szFileName, int mode = ios::in, int);
函数原型
ostream & seekp (int offset, int mode);
istream & seekg (int offset, int mode);
//mode有三种:ios::beg-开头往后offset(>=0)字节 ios::cur-当前往前(<=0)/后(>=0)offset字节 ios::end-末尾往前(<=0)offect字节
int tellg();
int tellp();
//seekg 函数将文件读指针定位到文件尾部,再用 tellg 函数获取文件读指针的位置,此位置即为文件长度
UNIX/Linux 平台中,用文本方式或二进制方式打开文件没有任何区别。
在 UNIX/Linux 平台中,文本文件以\n(ASCII 码为 0x0a)作为换行符号;而在 Windows 平台中,文本文件以连在一起的\r\n(\r的 ASCII 码是 0x0d)作为换行符号。
在 Windows 平台中,如果以文本方式打开文件,当读取文件时,系统会将文件中所有的\r\n转换成一个字符\n,如果文件中有连续的两个字节是 0x0d0a,则系统会丢弃前面的 0x0d 这个字节,只读入 0x0a。当写入文件时,系统会将\n转换成\r\n写入。
用二进制方式打开文件总是最保险的。
函数模板的一般形式:
template或template
函数类型 函数名(参数列表)
{
函数体;
}
template//class可以换成typename
函数类型 函数名(参数列表)
{
函数体;
}
//举个栗子
template T max(T a, T b)
{
return a > b ? a : b;
}
int main()
{
cout <<"max value is "<< max(12,34) << endl;//34
cout << "max value is " << max(12.4, 13.6) << endl;//13.6
cout << "max value is " << max(12.4, 13) << endl;//error 没有与参数列表匹配的 函数模板 "max" 实例参数类型为:(double, int)
return 0;
}
声明了类模板,就可以将类型参数用于类的成员函数和成员变量了。换句话说,原来使用 int、float、char 等内置类型的地方,都可以用类型参数来代替。
类模板的一般形式:
当类中的成员函数在类的声明之外定义时,它必须定义为函数模板,带上模板头,定义形式如下:
在模板引入 c++ 后,采用class来定义模板参数类型,后来为了避免 class 在声明类和模板的使用可能给人带来混淆,所以引入了 typename 这个关键字。
模板定义语法中关键字 class 与 typename 的作用完全一样。
不同的是typename 还有另外一个作用为:使用嵌套依赖类型(nested depended name)
class MyClass
{
public:
typedef int LengthType;
LengthType getLength() const
{
return this->length;
}
void setLength(LengthType length)
{
this->length = length;
}
private:
LengthType length;
};
template
void MyMethod(T myclass)
{
//告诉 c++ 编译器,typename 后面的字符串为一个类型名称,而不是成员函数或者成员变量
typedef typename T::LengthType LengthType; //
LengthType length = myclass.getLength();
cout << "length = " <
命名空间实际上是由用户自己命名的一块内存区域,用户可以根据需要指定一个有名字的空间区域,每个命名空间都有一个作用域,将一些全局实体放在该命名空间中,就与其他全局实体分割开来。
命名空间定义的一般形式:
命名空间成员的引用:命名空间名::命名空间成员名
使用命名空间别名:namespace 别名 = 命名空间名
使用using声明命名空间成员的格式:using 命名空间名::命名空间成员名;
使用using声明命名空间的全部成员:using namespace 命名空间名;
using声明后,在using语句所在的作用域中使用该命名空间成员时,不必再用命名空间名加以限定。
标准C++库的所有标识符(包括函数、类、对象和类模板)都是在一个名为std的命名空间中定义的。
无名的命名空间,只在本文件的作用域内有效。
C++标准模板库(Standard Template Library,STL)是泛型程序设计最成功的实例。STL是一些常用数据结构和算法的模板的集合,由Alex Stepanov主持开发,于1998年被加入C++标准。
C++ 标准模板库的核心包括三大组件:容器,算法,迭代器
顺序容器:可变长动态数组Vector、双端队列deque、双向链表list
关联容器:set、multliset、map、multimap
关联容器内的元素是排序的,所以查找时具有非常好的性能。
容器适配起:栈stack、队列queu、优先队列priority_queue
所有容器具有的函数:
顺序容器和关联容器函数:
顺序容器独有的函数:
迭代器是一种检查容器内元素并遍历元素的数据类型。C++更趋向于使用迭代器而不是下标操作,因为标准库为每一种标准容器(如vector)定义了一种迭代器类型,而只用少数容器(如vector)支持下标操作访问容器元素。按照定义方式分为以下四种。
STL 提供能在各种容器中通用的算法(大约有70种),如插入、删除、查找、排序等。算法就是函数模板。算法通过迭代器来操纵容器中的元素。
STL 中的大部分常用算法都在头文件 algorithm 中定义。此外,头文件 numeric 中也有一些算法。
许多算法操作的是容器上的一个区间(也可以是整个容器),因此需要两个参数,一个是区间起点元素的迭代器,另一个是区间终点元素的后面一个元素的迭代器。
会改变其所作用的容器。例如:
不会改变其所作用的容器。例如:
#include
#include
#include
using namespace std;
int main() {
vector v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4); //1,2,3,4
vector::iterator p;
p = find(v.begin(), v.end(), 3); //在v中查找3 若找不到,find返回 v.end()
if (p != v.end())
cout << "1) " << *p << endl; //找到了
p = find(v.begin(), v.end(), 9);
if (p == v.end())
cout << "not found " << endl; //没找到
p = find(v.begin() + 1, v.end() - 1, 4); //在2,3 这两个元素中查找4
cout << "2) " << *p << endl; //没找到,指向下一个元素4
int arr[10] = { 10,20,30,40 };
int * pp = find(arr, arr + 4, 20);
if (pp == arr + 4)
cout << "not found" << endl;
else
cout << "3) " << *pp << endl;
return 0;
}
可以关注微 信 公 众 号 “C和C加加” 回复“ZXC”领取详细教程和更多进阶视频教程大全