c++学习记录(部分)
关键字
关键字 | 作用 |
---|---|
override | 覆盖,如果派生类在虚函数声明时使用了override关键字,该函数必须重载基类(父类)中的同名函数。 |
inline | 内联函数,把函数指定为内联函数,解决频繁调用的函数消耗大量的栈空间(栈内存)的问题。 |
数据类型
基本的内置类型:
布尔型:
bool
字符型:
char
整型:
int
浮点型:
float
双浮点型:
double
无类型:
void
宽字符型:
wchar_t
【本质:typedef short int wchar_t;】
类型修饰符:
signed
、unsigned
、short
、long
;默认情况下,
int
、short
、long
都是带符号的,即signed int
、signed short
、signed long
;
typedef 声明
给数据类型关键字重命名。
语法:
typedef type newname;
例:
typedef int money; money minghave = 100; // 声明整型变量minghave初始值为100
枚举类型
枚举是c++的派生数据类型,如果一个变量只有几种可能的值,可以定义为枚举类型。枚举就是将变量的值一个一个的列举出来,该变量的值只能在列举出来的值范围内。
关键字:
enum
语法:
enum 枚举名{ 标识符[=整型常数], 标识符[=整型常数], ... 标识符[=整型常数] } 枚举变量;
示例:
enum color {red,green,blue} c; c = blue;
默认情况下,第一个名称值为0,第二个名称的值为1,第三个为2,以此类推。也可以给名称赋予一个特殊的值,只需要添加一个初始值即可。
示例:
enum color {red,green=5,red}; // ****green赋值为5之后,red为6,默认情况每个名称的值比前一个大1,red依然为0.
类型转换
四种类型转换方式:1、静态转换;2、动态转换;3、常量转换;4、重新解释转换。
静态转换(Static Cast)——强制类型转换
不进行任何运行时的类型检查,因此可能会导致运行时错误。
int i = 10; float f = static_cast<float>(i);//将int转换为float
动态转换(Dynamic Cast)
运行时进行类型检查,不能转换则返回空指针或引发异常。
// 动态转换通常用于将一个 基类指针或引用 转换为 派生类指针或引用。 class Base {}; class Derived : public Base {}; Base* ptr_base = new Derived; Derived* ptr_derived = dynamic_cast<Derived*>(ptr_base);// 将基类指针转换为派生类指针。
常量转换(Const Cast)
用于将const类型的对象转换为非const类型的对象。
常量转换只能用于转换掉const的属性,不能改变对象的类型。
const int i = 10; int& r = const_cast<int&>(i);// 常量转换,将const int 转换为int
重新解释转换(Reinterpret Cast)
将一个数据类型的值重新解释为另一个数据类型的值,通常用于在不同的数据类型之间的转换。
重新解释转换不进行任何检查,因此可能会导致未定义的行为。
int i = 10; float f = reinterpret_cast<float&>(i);// 将int类型转换为float
变量类型
在声明变量时,不带初始化的定义:带有静态存储持续时间的变量会被隐式初始化为NULL(所有字节的值都是0),其他所有变量的初始值是未定义的。
bool
、char
、 int
、float
、 double
、 void
、 wchar_t
。
变量作用域
-
在函数或一个代码块内部声明的变量,称为局部变量
#include<iostream> using namespace std; int main(){ int a, b; // 局部变量声明 a = 10; b = 20; // 变量初始化 cout<<a<<b<<endl; return 0; }
-
在函数参数的定义中声明的变量,成为形式参数
-
在所有函数外部声明的变量,成为全局变量
#include<iostream> using namespace std; int g = 20; int main(){ int g = 10; cout<< g << endl; // 10,局部变量的值覆盖全局变量的值。 return 0; }
-
局部作用域:在函数内部声明的变量具有局部作用域,它们只能在函数内部访问。局部变量在函数每次被调用时被创建,在函数执行完后被销毁。
-
全局作用域:在所有函数和代码块之外声明的变量具有全局作用域,它们可以被程序中的任何函数访问。全局变量在程序开始时被创建,在程序结束时被销毁。
-
块作用域:在代码块内部声明的变量,它们只能在代码块内部访问。块作用域变量在代码块每次被执行时被创建,在代码块执行完后被销毁。
-
类作用域:在类内部声明的变量具有类作用域,它们可以被类的所有成员函数访问。类作用域变量的生命周期与类的生命周期相同。
#include<iostream> class MyClass { public: static int class_var; // 类作用域变量 }; int MyClass::class_var = 30; int main(){ std::cout << "类变量:"<<MyClass::class_var<<std::endl; return 0; // 输出为:类变量:30 }
常量
-
整数常量
前缀为
0x
或0X
表示十六进制,0
表示八进制,不带前缀默认表示为十进制。整数常量后缀:
U
表示无符号整数(unsigned),L
表示长整数(long),后缀可以大写或者小写,U和L顺序任意。212 // 合法 215u // 合法 0xFeeL // 合法 078 // 非法,8不是八进制数字 032UU // 非法,不能重复后缀
85 // 十进制 0213 // 八进制 0x4b // 十六进制 30 // 整数,十进制 30u // 无符号整数 30l // 长整数 30lu // 无符号长整数
-
浮点常量
浮点常量有整数部分、小数点、小数部分和指数部分组成,可以使用小数形式或者指数形式表示浮点常量。
-
布尔常量
true
代表真,false
代表假。 -
字符常量
字符常量是
括在单引号
中的。当常量以
L
(仅大写)开头,则表示是一个宽
字符常量(例:L'a'
),此时它必须存储在wchar_t
类型的变量中。否则,它是一个窄
字符常量,此时它可以存储在char
类型的简单变量中。字符常量可以是一个普通的字符(例:
'x'
)、一个转义字符(例:'\t'
)或者一个通用的字符(例:'\u02C0'
)一些特定的字符:
转义序列 含义 \\ \字符 \' '字符 \" "字符 \? ?字符 \a 警报铃声 \b 退格键 \f 换页符 \n 换行符 \r 回车 \t 水平制表符 \v 垂直制表符 \ooo 一到三位的八进制数 \xhh... 一个或多个数字的十六进制数 #include<iostream> using namespace std; int main(){ cout<<"hello\tworld\n\n"<<"哇"<<endl; return 0; }
-
字符串常量
字符串字面值或常量时括在双引号
""
中的。一个字符串包含类似于字符常量的字符:普通的字符、转义序列和通用的字符。可以使用
\
做分隔符,把一个很长的字符串常量进行分行。#include<iostream> #include<string> using namespace std; int main(){ string greeting = "hello,c++"; cout<<greeting; cout<<"\n"; string greeting2 = "hello,\ c++"; cout<<greeting2<<endl; return 0; } // 运行结果相同。
-
常量的定义
c++编程的良好习惯:将常量定义为大写字母形式
在
c++
中有两种定义常量的方式;- 使用
#define
预处理器 - 使用
const
关键字
- 使用
-
#define 预处理器
// 语法 #define identifier value
示例:
#include<iostream> using namespace std; #define HEIGHT 10 #define WIDTH 5 #define NEWLINE '\n' int main(){ int area; area = HEIGHT * WIDTH; cout<<"面积:"<<NEWLINE<<area<<endl; return 0; }
运行结果
面积: 50
-
const关键字
// 语法 const type variable = value; // 示例 const int WIDTH = 5; const int HEIGHT = 10; const char NEWLINE = '\n';
C++修饰符类型
c++允许在char、int和double数据类型前放置修饰符。
signed
:表示变量可以存储负数。对于整型变量来说,signed可以省略,整型变量默认为有符号类型。
unsigned
:表示变量不能存储负数。
short
:变量范围比int
更小。short int
可缩写为short
long
:变量范围比int
更大。long int
可缩写为long
long long
:变量范围比long
更大,c++11新增的数据类型修饰符
float
:单精度浮点数
double
:双精度浮点数
bool
:布尔类型,只有true
和false
两个值
char
:表示字符类型
wchar_t
:表示宽字符类型,可以存储Unicode
字符
当signed、unsigned、long和short简写时,代表为int类型,int时隐含的。
signed、unsigned、long和short可应用于整型,
signed、unsigned可应用与字符型,
long可用于双精度型。
signed、unsigned、long 和short 可以组合使用。
无符号整数和有符号整数的差别,示例:
#include<iostream>
using namespace std;
int main(){
short int i; // 有符号短整数
short unsigned int j; // 无符号短整数
j = 50000;
i = j;
cout << i << "\t" << j << endl;
return 0;
}
// 结果为
// -15536 50000
//计算过程为:50000转为二进制数为:1100 0011 0101 0000,
// 最左边 1 表示负号,计算机以补码形式来表示数字,要得到原数字,先-1再取反,
// 得:0011 1100 1011 0000,即:-15536
C++中的类型限定符
-
const:
const
定义常量,表示该常量不能被修改。const int NUM = 10; // 定义常量NUM,值不能修改 const int* ptr = &NUM; // 定义指向常量的指针,指针所指的值不可修改 int const* ptr2 = &NUM; // 和上一行等价
-
volatile:表示定义的变量可能会被程序以外的因素改变,如硬件或其他线程。
volatile int num = 20; //定义变量num,值可能会在位置时间被改变
-
restrict:由
restrict
修饰的指针是唯一 一种访问它所指向的对象的方式。只有C99增加了该限定符 -
mutable:表示类中的成员变量可以在 const 成员函数中被修改。
class Example { public: int get_value() const { return value_; // const 关键字表示该成员函数不会修改对象中的数据成员。 } void set_value(int value) const { value_ = value; // mutable 关键字允许在 const 成员函数中修改成员变量 } private: mutable int value_; }
-
static:用于定义静态变量,表示该变量的作用域仅限于当前文件或当前函数内,不会被其他文件或函数访问。
void example_function(){ static int count = 0; // static 使变量 count 存储在程序生命周期内都存在。 count++; }
-
register:用于定义寄存器变量,表示该变量被频繁使用,可以存储在CPU的寄存器中,以提高程序的运行效率。
void example_function(register int num) { // register 关键字建议编译器将变量 num 存储在寄存器中 // 以提高程序执行速度 // 但是实际上是否会存储在寄存器中,由编译器决定 }
C++存储类
存储类定义C++程序中变量/函数的范围(可见性)和生命周期。这些说明符放在它们所修饰的类型之前。下面列出C++程序中可用的存储类
-
auto
:从C++17开始,auto关键字不再是C++存储类的说明符C++11开始,auto用于声明变量时根据初始化表达式自动推断变量的数据类型、声明函数时函数返回值的占位符。
C++98标准中auto关键字用于自动变量的声明,使用极少多余在C++17中删除
auto f = 3.14; // double auto s("hello"); // const char* auto z = new auto(9); // int* auto x1 = 5, x2 = 3.0, x3 = 'a'; // 错误用法,初始化应为同一种数据类型
-
register
:在C++17中,register关键字被弃用。register存储类用于定义存储在寄存器中而不是RAM中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的
'&'
运算符(因为没有内存位置)。{ register int miles; }
-
static
:static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。
static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。
在 C++ 中,当 static 用在类数据成员上时,会导致仅有一个该成员的副本被类的所有对象共享。
-
extern
:extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 ‘extern’ 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。
当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。
-
mutable
:mutable 说明符仅适用于类的对象。它允许对象的成员替代常量,即mutable 成员可以通过 const 成员函数修改。
-
thread_local(C++11)
:使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。
thread_local 说明符可以与 static 或 extern 合并。
可以将 thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义。
thread_local int x; // 命名空间下的全局变量 class X{ static thread_local std::string s; // 类的static成员变量 }; static thread_local std::string X::s; // X::s需要定义 void foo(){ thread_local std::vector<int> v; // 本地变量 }
C++运算符
-
算术运算符
+
:相加-
:相减*
:相乘/
:相除%
:取模运算符,整除后的余数++
:自增运算符,加1--
:自减运算符,减1 -
关系运算符
==
:检查两个操作数的值是否相等,相等为真,不等为假!=
:检查两个操作数的值是否不等,不等为真,相等为假>
:大于<
:小于>=
:大于等于<=
:小于等于 -
逻辑运算符
&&
:逻辑与运算,两个操作数同为真,则为真;有假则假。||
:逻辑或运算,两个操作数同为假,则为假;有真则真。!
:逻辑非运算,改变逻辑状态。 -
位运算符
&
:按位与运算,有0为0,同1为1。|
:按位或运算,有1为1,同0为0。^
:按位异或运算,相同为0,不同为1。~
:取反运算符,例: 60取反过程为: 60: 0011 1100(正数原码、补码) 所有位取反: 1100 0011(负数的补码) 减一变反码: 1100 0010(负数的反码) 数值位取反: 1011 1101(负数的原码)
<<
:二进制左移运算符,将运算对象的所有二进制位全部左移n位,最左边二进制位丢弃,最右边二进制位补0。例: 60 << 2; // 左移两位 过程为:0011 1100=》1111 0000
>>
:二进制右移运算符,将运算对象的二进制位全部右移n位,正数最左边补0,负数最左边补1,最右边丢弃。例: 60 >> 3; // 右移三位 过程为:0011 1100 =》0000 0111
-
赋值运算符
=
:将右边操作数的值赋给左边操作数。+=
:将右边操作数的值加上左边操作数的值赋值给左边操作数。-=
:将左边操作数的值减去右边操作数的值的结果赋值给左边操作数。*=
:将右边操作数与左边操作数相乘的结果赋值给左边操作数。/=
:将左边操作数与右边操作数相除的结果赋值给左边操作数。%=
:将左右两边的操作数取模运算后的结果赋值给左边操作数。<<=
:左移赋值。>>=
:右移赋值。&=
:按位与赋值。|=
:按位或赋值。^=
:按位异或赋值。 -
杂项运算符
sizeof
:返回变量的大小,类似字节数。? :
:三目运算符(条件运算符)。,
:逗号运算符。.
和->
:成员运算符,用于引用类、结构和共用体的成员。强制转换运算符:例:
(int)2.34
,结果为2
。&
:指针运算符,返回变量的地址,例:&a,返回变量a的实际地址(内存地址)。*
:指针运算符,指向一个变量。 -
运算符优先级
后缀 > 一元 > 乘除 > 加减 > 移位 > 关系 > 相等 > 位与运算 > 位异或 > 位或 > 逻辑与 > 逻辑异或 > 逻辑或 > 三目运算符 > 赋值运算符 > 逗号运算符
C++循环
while
循环:for
循环:do...while
循环:- 嵌套循环:
- 循环控制语句:
break
continue
goto
C++判断
if
语句:if...else...
语句:- 嵌套
if
语句: switch
语句:- 嵌套switch语句:
- 三目运算:
...?...:...
C++函数
函数的声明与定义。
*Lambda函数与表达式:也称匿名函数、Lambda表达式。
Lambda函数,把函数看作对象,可以像对象一样使用,比如可以将它们赋值给变量或作为参数传递,还可以像函数一样对其求值。
例:
// 语法: [capture](parameters)->return-type{body} // 示例1 [](int x, int y){ return x< y; } // 示例2 [](int x, int y)->int { int z = x + y; return z + x; }
在Lambda函数中,可以访问当前作用域的变量,这是Lambda函数的
闭包
(Closure)行为,与JavaScript闭包不同,c++变量传递有传值和传引用的区别,可以通过[]
来指定。
C++数字相关
-
C++数学运算:
使用C++内置的数学函数,需要引用头文件
<cmath>
。cos(double)
:余弦函数,double型sin(double)
:正弦函数,double型tan(double)
:正切函数,double型log(double)
:对数函数,double型pow(double, double)
:pow(x, y),求x的y次方hypot(double, double)
:hypot(x, y),求 x2 + y2 的平方根,类似求直角三角形的斜边长。sqrt(double)
:返回参数的平方根abs(int)
:返回整数的绝对值fabs(double)
:返回任意一个浮点数的绝对值。经过测试,abs() 也可以返回浮点数的绝对值。
floor(double)
:向下取整floor(12.3234); // 12 floor(12.9999); // 12 floor(-12.00012); // -13 floor(-12.999); // 13
-
c++随机数:
使用函数
rand()
,使用之前需要先调用srand()
函数。#include<iostream> #include<ctime> #include<cstdlib> using namespace std; int main(){ int i, j; srand((unsigned)time(NULL)); for(i=0;i<10;i++){ j = rand(); cout<<"随机数:"<<j<<endl; } return 0; }
C++数组
语法:
type arrayName[arraySize];
示例:
// 声明 int nums[10]; // 初始化 int nums[4] = { 3, 2, 5, 8 }; // 正确 int numbers[3] = { 1, 4, 2, 3 }; // 错误初始化,初始化的元素长度为4,声明的数组长度为3,个数不能超过3。 int ages[] = { 19, 18, 22}; // 正确初始化,数组未规定长度,长度以实际初始化长度为准。
-
多维数组
示例:
// 二维整型数组 int twodim[3][2] = {{1,2},{3,4},{5,6}}; int a[3][2] = {1,2,3,4,5,6}; // 以上两种初始化等同,内嵌的括号可选。 // 三维整型数组 int threedim[4][2][3];
-
指向数组的指针
示例:
double a[10]; double *p = a;
解释:
以上声明的双精度型数组
a
,是一个指向&a[0]
的指针,即数组 a 的第一个元素的地址,因此上面把 p 赋值为 数组 a 的第一个元素的地址。// 以下等价 *p = a[0]; *(p+1) = a[1] = *(a+1); *(p+2) = a[2] = *(a+2); ......
-
传递数组给函数
c++传数组给函数,会把数据类型自动转换为指针类型,因此实际传的是地址。
语法:
// 方式一 void myFunction(int *param){} // 方式二 void myFunction(int param[3]){} // 方式三 void myFunction(int param[]){}
-
从函数返回数组
c++不允许返回一个完整的数组作为函数的参数,但是,可以通过指定的不带索引的数组名来返回一个数组的指针。
如果想要从函数返回一个一维数组,则必须声明函数为指针类型。
示例:
int* myFuncion(){}
不能简单地返回指向局部数组的指针,因为当函数结束时,局部数组将被销毁,指向它的指针将变得无效。
C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。
为了避免以上情况,可以使用静态数组或者动态分配数组。
实例:使用静态数组#include<iostream> #include<cstdlib> #include<ctime> using namespace std; int* getRandom(){ static int r[10]; srand((unsigned)time(NULL)); for(int i=0;i<10;i++){ r[i] = rand(); cout<<r[i]<<endl; } return r; } int main(){ int *p; p = getRandom(); for(int i = 0;i<10;i++){ cout<<"*(p+"<<i<<"):"; cout<<*(p+i)<<endl; } return 0; }
实例:使用动态分配数组
使用动态分配数组需要在函数内部使用 new 运算符来分配一个数组,并在函数结束时使用 delete 运算符释放该数组
#include <iostream> using namespace std; int* createArray(int size) { int* arr = new int[size]; for (int i = 0; i < size; i++) { arr[i] = i + 1; } return arr; } int main() { int* myArray = createArray(5); for (int i = 0; i < 5; i++) { cout << myArray[i] << " "; } cout << endl; delete[] myArray; // 释放内存 return 0; }
C++字符串
c++的字符串,在末尾存储了一个空字符
\0
,因此字符串的大小比看到的长度多一。char site[5]={'a','b','c','d','\0'}; // 等价于 char site[]="abcd";
C++指针(*)
在c++中,每个变量都有一个内存位置,通过运算符
&
访问内存地址。int a = 10; float b = 3.14; double c = 5.09; cout << &a << endl; cout << &b << endl; cout << &c << endl;
指针的定义:
指针是一个变量,值为另一个变量的地址,即内存位置的直接地址。
在使用指针存储其他变量的内存地址使,必须先声明。
int a = 20; int *p; p = &a; // 将变量a的内存地址赋值给指针变量p cout<<p<<endl; // 输出变量a的内存地址 cout<<*p<<endl; // 输出变量a的值,即该内存地址中存储的值;
c++ Null指针:
在变量声明的时候,如果没有确切的地址可以赋值,可以给指针变量赋一个NULL值,称为
空指针
。#include<iostream> int main(){ int *p = NULL; std::cout<<"p值为:"<<p<<endl; return 0; } // 结果为:p值为:0
指针算术运算:
可以使用四种算术运算:
++
--
+
-
。int *p; p++; // p在原有的地址+4,一个int型占4个字节 double *p2; p2++; // p2在原有地址+8,一个double型占8个字节
指针和数组:
注意点
int arr[3] = {100,200,300}; int* p; p = arr; p++; // 合法 arr++; // 不合法,arr是指向数组开头的常量,不能作为左值
指针数组:
int arr[3] = {10,20,30}; int* prr[3]; for(int i = 0; i < 3; i++){ prr[i] = &arr[i]; // 将数组arr中的每一个元素的内存地址赋值给指针数组prr }
指向指针的指针:
int a = 100; int* p1 = &a; int** p2 = &p1; // a = *p1 = **p2 // p1存放变量a的地址 // p2存放指针变量p1的内存地址 // *p1为变量a的值 // *p2为指针变量p1的值 // **p2为变量a的值
传递指针给函数:
从函数返回指针:
C++引用
- 引用和指针的区别
- 不存在空引用。引用必须连接到一个合法的内存。
- 一旦引用被初始化为一个对象时,就不能被指向另一个对象。指针可以在任何时候指向到另一个对象。
- 引用必须在创建时初始化。指针可以在任何时候被初始化。
int a = 10;
double b = 3.14;
int& m = a;
double& n = b;
cout<<m<<endl; // 10
cout<<n<<endl; // 3.14
引用是变量的别名。
引用作为参数,比传一般的参数更安全。
在函数中返回引用,和返回其他数据类型一样。
#include<iostream>
using namespace std;
void swap(int& x, int& y){
int temp;
temp = x;
x=y;
y=temp;
return;
}
int main(){
int a = 100;
int b = 200;
cout<<"a:"<<a<<"\n"<<"b:"<<b<<endl;
swap(a,b);
cout<<"a:"<<a<<"\n"<<"b:"<<b<<endl;
return 0;
}
C++中的日期和时间
使用时间和日期函数需要引用头文件<ctime>
> 四个与时间相关的类型:`clock_t` `time_t` `size_t` `tm` 。 > > `clock_t` `size_t` `time_t` 能够把系统时间和日期表示为某种整数。 > > `tm` 的结构定义为: > > ```c++ > struct tm { > int tm_sec; // 秒,0~59 > int tm_min; // 分,0~59 > int tm_hour; // 小时,0~23 > int tm_mday; // 一个月中的第几天,1~31 > int tm_mon; // 月,0~11 > int tm_year; // 年数,从1900年开始 > int tm_wday; // 一周中的第几天,0~6 > int tm_yday; // 一年中的第几天,0~365 > int tm_isdst; // 夏令时 > } > ```C++的基本输入输出
-
I/O
库头文件<iostream>
:定义了cin
(标准输入流)、cout
(标准输出流)、cerr
(非缓冲标准错误流)、clog
(缓冲标准错误流、标准日志流) 对象。<iomanip>
:该文件通过所谓的参数化的流操纵器(如:setw
和setprecision
),来声明对执行标准化I/O
有用的服务。<fstream>
:该头文件为用户控制的文件处理声明服务。
// cerr 用法:
char str[] = "Unable to read that you are a pretty man ...";
cerr << "Error message : " << str << endl;
// clog 用法:
char ss[] = "Unable to read that you are you are ...";
clog << "Error message : " << ss << endl;
使用cerr显示错误消息,使用clog显示日志信息,使用cout显示其他信息。
数据结构
数据结构是一种用户自定义的可用的数据类型,允许存储不同类型的数据项。
示例:
struct Books {
char title[50];
char author[50];
char subject[100];
int book_id;
};
// Books - 为结构体类型的名称
实例:
#include<iostream>
#include<cstring>
using namespace std;
struct Books{
char title[50];
char author[50];
int book_id;
};
int main(){
Books book1;
strcpy(book1.title,"三体");
strcpy(book1.author,"刘慈欣");
book1.book_id = 1000;
cout<<book1.title<<endl;
cout<<book1.author<<endl;
cout<<book1.book_id<<endl;
return 0;
}
-
结构作为函数参数
实例:
void printBook(struct Books book); struct Books{ char title[50]; char author[50]; int book_id; }; int main(){ Books book1; strcpy(book1.title, "三体"); strcpy(book1.author, "刘慈欣"); book1.book_id = 1000; printBook(book1); return 0; } void printBook(struct Books book){ cout<<book.title<<endl; cout<<book.author<<endl; cout<<book.book_id<<endl; }
-
指向结构的指针
语法:
struct Books *pBook;
示例:
pBook = &book1;
使用结构的指针访问结构中的成员使用运算符
->
。pBook->title;
实列:
void printBook(struct Books *book); struct Books{ char title[50]; char author[50]; int book_id; }; int main(){ Books book1; strcpy(book1.title, "三体"); strcpy(book1.author, "刘慈欣"); book1.book_id = 1000; printBook(&book1); return 0; } void printBook(struct Books *book){ cout<< book->title <<endl; cout<< book->author <<endl; cout<< book->book_id <<endl; }
-
typedef关键字
使用typedef给定义的结构体取一个别名。
typedef struct Books{ char title[50]; char author[50]; int book_id; }InfoBooks; InfoBooks book2;
使用typedef定义非结构类型:
typedef long int *pint32; pint32 x, y, z; // x,y,z是指向长整型long int的指针。
---------- C++面向对象 ----------
C++类和对象
类用于指定对象的形式,是一种用户自定义的数据类型,它是一种封装了数据和函数的组合。类中的数据称为成员变量,函数成为成员函数。类可以被看作是一种模板,可以用来创建具有相同属性和行为的多个对象。
-
类的定义
使用关键字
class
,然后指定类的名称,类的名称最好以大写字母开头。class classname{ Access specifiers: // 访问修饰符:private/public/protected Date members/variables; // 变量 Member function(){} // 方法/函数 }; // 分号结束一个类
-
类示例
// 创建类 class Box{ public: double width; double height; double breadth; }; // 声明类的对象 Box box1; Box box2;
-
类实例
#include<iostream> using namespace std; class Box{ public: double width; double height; double breadth; double get(void); void set(double wid, double hei, double bre); }; // 成员函数定义 double Box::get(void){ return width*height*breadth; } void Box::set(double wid, double hei, double bre){ width = wid; height = hei; breadth = bre; } int main(){ Box box1; Box box2; double volume = 0.0; box1.width = 5.0; box1.height = 5.0; box1.breadth = 7.0; volumn = box1.width * box1.height * box1.breadth; cout<<"box1体积:"<<volumn<<endl; box2.set(16.0,13.0,4.0); volumn = box2.get(); cout<<"box2体积:"<<volumn<<endl; return 0; }
-
类成员函数
类的成员函数是指那些把定义和原型写在类定义内部的函数,和类定义中的其他变量一样。
类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。
成员函数可以定义在类定义内部,或者使用范围解析运算符
::
定义。使用::
时,必须在元素安抚前面使用类名。class Box{ public: double width; double height; double getVolumn(){ return 20.0; } void set(double wid, double hei); }; void Box::set(double wid,double hei){ width = wid; height = hei; }
成员函数的调用
Box box1; box1.set(2.0, 4.2);
-
类访问修饰符
public
:公有成员,程序中的类外部可以访问,可以不使用成员函数来设置和获取公有变量的值。protected
:受保护成员,与私有成员类似,不同点在与,受保护成员,在派生类(即子类)中可访问。private
:私有成员,成员和类的默认访问修饰符。私有成员变量和成员函数,在类的外部不可访问、不可查看。只有类和友元函数可以访问私有成员。类的使用过程中,一般将数据定义在私有区域,将函数定义在公有区域。
类访问修饰符在*继承*中的特点:
public
继承:基类中的public成员、protected成员、private成员的访问属性,分别变成public、protected、private。基类 派生类 public -> public protected -> protected private -> private
protected
继承:基类 派生类 public -> protected protected -> protected private -> private
private
继承:基类 派生类 public -> private protected -> private private -> private
总结:
1、private成员只能被本类成员(类内)和友元访问,不能被派生类访问;
2、protected成员可以被派生类访问。
-
构造函数和析构函数
构造函数:
构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时被执行。
构造函数的名称与类名完全相同,并且不会返回任何类型,也不会返回void。
构造函数可用于为某些成员变量设置初始值。
实例:
#include<iostream> using namespace std; class Line{ public: void setLength(double len); double getLength(void); Line(); // 这是构造函数 private: double length; }; Line::Line(){ cout<<"构造函数被创建"<<endl; } void Line::setLength(double len){ length = len; } double Line::getLength(void){ return length; } int main(){ Line line01; line01.setLength(6.0); cout<<"长度为:"<<line01.getLength()<<endl; return 0; } // 执行结果为: // 构造函数被创建 // 长度为:6
带参数的构造函数
实例:
#include<iostream> using namespace std; class Line{ public: void setLength(double len); double getLength(); Line(double len); private: double length; }; Line::Line(double len){ cout<<"类对象的长度为:"<<len<<endl; length = len; } void Line::setLength(double len){ length = len; } double Line::getLength(){ return length; } int main(){ Line line02(12.00); cout<<"类line02的长度为:"<<line.getLength()<<endl; line.setLength(9); // 修改长度 cout<<"类line02修改后的长度为:"<<line.getLength()<<endl; return 0; } /*输出结果为: *类对象的长度为:12 *类line02的长度为:12 *类line02修改后的长度为:9 */
使用初始化列表 初始化字段
实例:
Line::Line(double len): length(len){ cout<<"类对象的长度为:"<<len<<endl; } // 等价于 Line::Line(double len){ length = len; cout<<"类对象的长度为:"<<len<<endl; } // 语法: C::C(double a, double b, double c):X(a),Y(b),Z(c){}
析构函数
析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称也与类的名称完全相同,只是需要加一个波浪号(
~
)作为前缀,不返回任何值,不能带有参数。析构函数有助于跳出程序(如:关闭文件、释放内存等)前释放资源。
实例:
#include<iostream> using namespace std; class Line{ public: void setLength(double len); double getLength(void); Line(); // 构造函数的声明 ~Line(); // 析构函数的声明 private: double length; }; Line::Line(void){ cout<<"类的对象已被创建......"<<endl; } Line::~Line(){ cout<<"类的对象已被删除......"<<endl; } void Line::setLength(double len){ length = len; } double Line::getLength(void){ return length; } int main(){ Line line03; line03.setLength(3.0); cout<<"Length of line03 : "<<line03.getLength()<<endl; return 0; }
-
拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。
通常用于:
1、通过使用另一个同类型的对象来初始化新创建的对象。
2、复制对象把它作为参数传递给函数。
3、复制对象,并从函数返回这个对象。
如果类中没有定义拷贝构造函数,编译器会自行定义一个。
如果类带有指针变量,并且有动态内存分配,则它必须有一个拷贝构造函数。
常见语法:
classname(const classname &obj){ // 构造函数的主体 }
实例:
#include<iostream> using namespace std; class Line{ public: int getLength(); Line(int len); // 构造函数 Line(const Line &obj); // 拷贝构造函数 ~Line(); // 析构函数 private: int *ptr; }; Line::Line(int len){ cout<<"构造函数,创建对象成功。"<<endl; // 为指针分配内存 ptr = new int; *ptr = len; } Line::Line(const Line &obj){ cout<<"拷贝构造函数,为指针ptr分配内存"<<endl; ptr = new int; *ptr = *obj.ptr; // 拷贝值 } Line::~Line(){ cout<<"析构函数,删除创建的对象成功。"<<endl; delete ptr; } int Line::getLength(){ return *ptr; } void display(Line obj){ cout<<"line大小:"<<obj.getLength()<<endl; } int main(){ Line line06(10); display(line06); return 0; } // 这没有记录完。
-
友元函数
友元函数可以访问类的private和protected成员。
友元函数定义在类的外部,不是类的成员函数
友元可以是一个函数(友元函数),也可以是一个类(友元类)。
声明类的友元,需要在类定义中,该友元函数的原型前使用关键字
friend
。// class Box{ public: double length; friend void printWidth(Box box01); // 友元函数的声明 void setWidth(double wid); friend class bigBox; // 友元类的声明。 private: double width; };
实例:
#include<iostream> using namespace std; class Box{ public: friend void printWidth(Box box); void setWidth(double wid); private: double width; }; void Box::setWidth(double wid){ width = wid; } void printWidth(Box box){ cout<<"width of box : "<<box.width<<endl; } int main(){ Box box1; box1.setWidth(10); printWdith(box1); return 0; }
-
内联函数
内联函数通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。
修改内联函数后,需要重新编译函数的所有客户端,编译器需要重新跟换所有代码,否则客户端会继续使用旧的函数。
将一个函数定义为内联函数,需要添加关键字
inline
作为前缀,调用函数前需要对函数进行定义。如果定义的函数多于一行,编译器会忽略inline限定符。在类的定义中,定义的函数都是内联函数,即使没有使用inline关键字。
实例:
#include<iostream> using namespace std; inline int Max(int x, int y){ return (x>y)?x:y; } int main(){ cout<<"最大值为:"<<Max(20,10)<<endl; }
-
this指针
在c++中,每个对象都可以通过
this
指针访问自己的地址。this 指针是所有成员函数的隐含函数。在成员函数内部,可以使用 this 指向调用的对象。友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。
实例:
#include<iostream> using namespace std; 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 volumn(){ return length * breadth * height; } int compare(Box box){ return this->volumn() > box.valumn(); } private: double length; double width; double height; }; int main(){ Box box1(3,2,1); Box box2(9,6,3); if(box1.compare(box2)){ cout<<"box2 is big than box1"<<endl; }else{ cout<<"box2 id equal to of larger than Box1"<<endl; } return 0; }
-
指向类的指针。
指向c++类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符
->
,使用指针前需要进行初始化。实例:
#include<iostream> using namespace std; class Box{ public: Box(double l=2.0, double b=2.0,double h=2.0){ cout<<"构造函数创建"<<endl; length = l; breadth = b; height = h; } double Volume(){ return length*breadth*height; } private: double length; double breadth; double height; }; int main(){ Box box1(3.0,5.0,4.0); Box *ptrBox; ptrBox = &box1; cout<<"box1体积:"<<ptrBox->Volume()<<endl; return 0; }
-
类的静态成员
可以使用
static
关键字,将类成员定义为静态的,意味着无论创建多少个类的对象,静态成员都只有一个副本。静态成员变量
静态成员在类的所有对象中是共享的。
如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会初始化为
零
。不能把静态成员的初始化放在类的定义中,但是可以在类的外部通过使用范围解析运算符
::
来重新声明静态变量从而对它进行初始化。实例:
#include<iostream> using namespace std; class Box{ public: static int objectCount; Box(double l=2,double b=2,double h=2){ cout<<"构造函数的创建"<<endl; length = l; breadth = b; height = h; objectCount ++; // 每次创建对象时加一 } double Volume(){ return length * breadth * height; } private: double length; double breadth; double height; }; int Box::objectCount = 0; // 初始化类的静态成员 int main(){ Box box1(1,3,2); Box box2(4,3,2); cout<<"objectCount : "<<Box::objectCount <<endl; return 0; } // 输出结果为 // objectCount : 2
静态成员函数
静态成员函数可以把函数与类的任何特定对象独立开来。
静态成员函数在类对象不存在的情况下也能被调用,
静态成员函数使用类名加范围解析运算符
::
就可以访问。静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。
静态成员函数不能访问类的
this
指针。可以使用静态成员函数判断类的某些对象是否已经被创建。
静态成员函数与普通成员函数的区别:
1、静态成员函数没有
this
指针,只能访问静态成员(包括静态成员变量和静态成员函数)。2、普通成员函数有
this
指针,可以访问类中的任意成员。实例:
#include<iostream> using namespace std; class Box{ public: static int objectCount; Box(double l=2,double b=2,double h=2){ cout<<"构造函数创建"<<endl; length = l; breadth = b; height = h; objectCount ++ ; } double Volume(){ return length*breadth*height; } static int getCount(){ return objectCount; } private: double length; double breadth; double height; }; int Box::objectCount = 0; int main(){ cout<<"创建对象前objectCount: "<<Box::getCount()<<endl; Box box1(1,2,3); Box box2(3,4,2); cout<<"创建对象后objectCount: "<<Box::getCount()<<endl; } // 运行结果 // 创建对象前objectCount: 0 // 创建对象后objectCount: 2
继承
继承允许依据一个类来定义另一个类
当创建一个类时,指定新建的类继承了一个已有类的成员即可。
已有的类称为基类(父类),新建的类称为派生类(子类)。
语法:
class paiClass : [private/public/protected] jiClass paiClass 为派生类类名 [private/public/protected] 三选一,默认为private jiClass 基类类名
实例:
#include<iostream> using namespace std; // 基类 class Shape{ public: void setWidth(int w){ width = w; } void setHeight(int h){ height = h; } protected: int width; int height; }; class Rectangle: public Shape{ public: int getArea(){ return (width * height); } }; int main(){ Rectangle Rect; Rect.setWidth(5); Rect.setHeight(4); cout<<"面积:"<<Rect.getArea()<<endl; return 0; } // 结果 // 面积:20
派生类的访问权限
派生类可以访问基类的public、protected。
一个派生类继承了所有的基类方法,但是:
1、派生类不能访问基类的构造函数、析构函数和拷贝构造函数;
2、不能访问基类的重载运算符
3、不能访问基类的友元函数
继承类型
通常使用
public
继承,几乎不使用protected
和private
。继承规则:
公有继承(public):
基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以调用基类的公有和保护成员来访问。
保护继承(protected):
基类的公有和保护成员成为派生类的保护成员。
私有继承(private):
基类的公有和保护成员成为派生类的私有成员。
多继承
一个子类有 多个父类
语法:
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,...{ // 内容 }
实例:
#include<iostream> using namespace std; class Shape{ public: void setWidth(int w){ width = w; } void setHeight(int h){ height = h; } protected: int width; int height; }; class PaintCost{ public: int getCost(int area){ return area*70; } }; // 派生 class Rectangle: public Shape, public PaintCost{ public: int getArea(){ return width*height; } }; int main(){ Rectangle Rect; int area; Rect.setWidth(2); Rect.setHeight(4); area = Rect.getArea(); cout<<"面积:"<<Rect.getArea()<<endl; cout<<"费用:"<<Rect.getCost(area)<<endl; return 0; } // 结果 // 面积:8 // 费用:560
重载函数和重载运算符
C++允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。
重载声明是指一个与之前已经在该作用域内声明过的函数数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。
当调用一个重载函数或重载运算符时,编译器通过所使用的参数类型与定义中的参数类型进行比较,选用最合适的定义。
选择最合适的重载函数或重载运算符的过程,称为重载决策。
函数重载
在同一个作用域中,声明几个功能类似的同名函数的形式参数(参数的个数、类型或者顺序)必须不同。
不能仅通过返回类型的不同来重载函数。
实例:
#include<iostream> using namespace std; class PrintData{ public: void print(int i){ cout<<"整数:"<<i<<endl; } void print(double f){ cout<<"浮点数:"<<f<<endl; } void print(char c[]){ cout<<"字符串:"<<c<endl; } }; int main(){ PrintData pd; pd.print(5); pd.print(3.1415); char s[]="Hey,just do it" pd.print(s); return 0; }
运算符重载
可以重定义或重载大部分C++的内置运算符。
重载运算符时带有特殊名称的函数,函数名有关键字
operator
和其后要重载的运算符符号构成。语法:
classname operator+(const classname&);
实例:
#include<iostream> using namespace std; 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; }; int main(){ Box box1; Box box2; Box box3; double volume = 0.0; box1.setLength(3); box1.setBreadth(2); box1.setHeight(5); cout<<box1.getVolume()<<endl; // 30 box2.setLength(5); box2.setBreadth(4); box2.setHeight(8); cout<<box2.getVolume()<<endl; // 160 box3 = box1+box2; cout<<box3.etVolume()<<endl; // 624 return 0; }
可重载运算符/不可重载运算符
可重载的运算符:
双目运算符 +(加)、-(减)、*(乘)、/(除)、%(取模) 关系运算符 ==(等于)、!=(不等于)、<(小于)、>(大于)、<=(小于等于)、>=(大于等于) 逻辑运算符 ||(逻辑或)、&&(逻辑与)、!(逻辑非) 单目运算符 +(正)、-(负)、*(指针)、&(取地址) 自增自减运算符 ++(自增)、--(自减) 位运算符 |(按位或)、&(按位与)、~(按位取反)、^(按位异或)、<<(左移)、>>(右移) 赋值运算符 =、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>= 空间申请与释放 new、delete、new[]、delete[] 其他运算符 ()(函数调用)、->(成员访问)、,(逗号)、[](下标) 不可重载的运算符:
.
:成员运算符
.*
、->*
:成员指针访问运算符
::
:域运算符
sizeof
:长度运算符
?:
:条件运算符
#
:预处理符号
多态
多态字面意思是多种形态。
当类之间是通过继承进行关联时,会用到多态。
多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
实例:
#include<iostream> using namespace std; 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); } }; int main(){ Shape *shape; Rectangle rec(10,7); Triangle tri(10,5); shape = &rec; shape->area(); shape = &tri; shape->area(); return 0; }
虚函数
虚函数,是在基类中使用关键字
virtual
声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编辑器不要静态链接到该函数。我们在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接 或 后期绑定 。
纯虚函数
当想在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
实例:改上面的Shape类中的area()函数
class Shape{ protected: int width,height; public: Shape(int a=0,int b =0){ width = a; height = b; } virtual int area() = 0;// =0,等于告诉编译器,函数没有主体,上面的虚函数是纯虚函数。 }
数据抽象
数据抽象是指,指向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。
数据抽象是一种依赖于接口和实现分离的编程(设计)技术。
在C++中,使用类来定义自己的抽象数据类型(
ADT
)。使用类
iostream
的cout
对象来输出数据。
实例:
#include<iostream>
using namespace std;
int main(){
cout<<"hello C++"<<endl;
return 0;
}
访问标签强制抽象
在c++中,使用访问标签来定义类的抽象接口。一个类可以包含零个或多个访问标签:
- 使用公共标签定义的成员都可以访问该程序的所有部分。一个类型的数据抽象视图是由它的公共成员来定义的。
- 使用私有标签定义的成员无法访问到使用类的代码。私有部分对使用类型的代码隐藏了实现细节。
数据抽象的好处
- 类的内部受保护,不会因无意的用户级错误导致对象状态受损。
- 类实现可能随时间的推移而发生变化,以便应对不断变化的需求,或者对那些要求不改变用户级代码的错误报告。
数据抽象实例
#include<iostream>
using namespace std;
class Adder{
public:
// 构造函数
Adder(int i=0){
total = i;
}
// 对外的接口
void addNum(int number){
total += number;
}
// 对外的接口
int getTotal(){
return total;
}
private:
// 对外隐藏的数据
int total;
};
int main(){
Adder a;
a.addNum(10);
a.addNum(20);
a.addNum(30);
cout << "Total : "<< a.getTotal() << endl; // 60
return 0;
}
数据封装
C++程序的两个基本要素:
程序语句(代码):是程序中执行动作的部分,被成为函数。
程序数据:数据是程序的信息,会受到程序函数的影响。
接口(抽象类)
C++接口是使用抽象类来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关的数据分离的概念。
如果一个类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用
=0
来指定的。
---------- 高级教程 ----------
C++文件和流
从文件读取流和向文件写入流,使用C++标准库
fstream
,
fstream
定义了三个数据类型:
ofstream
:表示输出文件流,用于创建文件并向文件写入信息。ifstream
:表示输入文件流,用于从文件读取信息。fstream
:通常表示文件流,同时具有ofstream
和ifstream
两种功能,可以创建文件,向文件写入信息,从文件读取信息。使用时,需要包含头文件
<iostream>
和<fstream>
。
打开文件
从文件读取信息或者像文件写入信息之前,必须先打开文件。
ofstream
和fstream
用来打开文件进行写
操作,
ifstream
用来打开文件进行读
操作。
open()
函数是fstream、ifstream和ofstream对象的一个成员。语法:
void open(const char *filename, ios::openmode mode); // 第一参数指定要打开的文件名称和位置。 // 第二参数定义文件被打开的模式。
模式标志及描述:
ios::app
:追加模式,所有写入都追加到文件末尾。ios::ate
:文件打开后定位到文件末尾。ios::in
:打开文件用于读取。ios::out
:打开文件用于写入。ios::trunc
:如果该文件已经存在,其内容将在打开文件之前被截断,即文件长度设为0。可以将以上两种或两种以上模式结合使用。例如,想要以写入模式打开文件,并希望截断文件,以防文件已存在,可以使用
ofstream outfile; outfile.open("file.dat", ios::out | ios::trunc);
打开文件用于读写:
ifstream afile; afile.open("file.dat", ios::out | ios::in );
关闭文件
// close()函数是fstream、ifstream、ofstream对象的一个成员。
void close();
写入文件
在C++编程中,使用流插入运算符(
<<
)向文件写入信息,就像使用该运算符输出信息到屏幕上一样。不同在于,这里使用ofstream
或fstream
,而不是cout
。
读取文件
在C++编程中,使用流提取运算符(
>>
)从文件读取信息,不同在于,使用
ifstream
或fstream
,而不是cin
。
读取和写入实例
#include<fstream>
#include<iostream>
using namespace std;
int main(){
char data[100];
// 写模式打开文件
ofstream outfile;
outfile.open("afile.dat");
cout<<"Writing to the file"<<endl;
cout<<"Enter your name: ";
cin.getline(data, 100); // 从外部读取一行
// 向文件写入用户输入的数据
outfile << data << endl;
cout<<"Enter your age: ";
cin>>data;
cin.ignore(); // 忽略掉之前读语句留下的多余字符
// 再次向文件写入用户输入的数据
outfile<<data<<endl;
// 关闭打开的文件
outfile.close();
// 以读模式打开文件
ifstream infile;
infile.open("afile.dat");
cout<<"Reading from the file"<<endl;
// 读取文件内容
infile >>data;
// 在屏幕上写入读取的文件内容
cout<<data<<endl;
// 再次从文件读取数据,并显示它
infile >> data;
cout<<data<<endl;
// 关闭文件
infile.close();
return 0;
}
文件位置指针
定位文件位置指针的成员函数
seekg()
:seek get
,第一个参数通常是一个长整型,第二个参数用于指定查找方向。
seekp()
:seek put
,第一个参数通常是一个长整型,第二参数用于指定查找方向。查找方向的模式标志及含义:
ios::beg
:默认值,从流的开头开始定位ios::cur
:从流的当前位置开始定位ios::end
:从流的末尾开始定位
// 定位到fileObject的第n个字节,ios::beg
fileObject.seekg(n);
// 把文件的读指针从 fileObject 当前位置向后移 n 个字节
fileObject.seekg(n, ios::cur);
// 把文件的读指针从 fileObject 末尾往回移 n 个字节
fileObject.seekg(n, ios::end);
// 定位到 fileObject 的末尾
fileObject.seekg( 0, ios::end );
异常处理
C++异常处理涉及到三个关键字:try、catch、throw。
- throw:当问题出现时,程序会抛出一个异常。
- catch:在想要处理问题的地方,通过异常处理程序捕获异常。
- try: try代码块中的代码标识将被激活的特定异常。后面通常跟着一个或多个catch块。try块中的代码,称为,保护代码。
try{
// 保护代码
} catch(ExceptionName e1){
// catch块
} catch(ExceptionName e2){
// catch块
} catch(ExceptionName e3){
// catch块
}
// 多个 catch 块,用于捕获不同类型的异常
抛出异常
可以使用 throw 语句在代码块中的任何地方抛出异常。
throw 语句的操作数可以是任意表达式,表达式结果的类型决定了抛出的异常的类型。
double division(int a,int b){
if(b == 0){
throw "Division by zero condition!";
}
return (a/b);
}
捕获异常
catch 块跟在 try 块后面,用于捕获异常。
指定想要捕获的异常类型,这是由catch关键字后的括号内的异常声明决定。
try{ // 保护代码 } catch{ // 处理异常 }
如果想让catch块能够处理抛出的任何类型的异常,则必须使用省略号
...
:try{ // 保护代码 } catch(...){ // 处理任何异常 }
实例:
#include<iostream>
using namespace std;
double division(int a,int b){
if(b==0){
throw "Division by zero condition!";
}
return (a/b);
}
int main(){
int x = 50;
int y = 0;
double z = 0;
try{
z = division(x,y);
cout<<z<<endl;
}catch(const char* msg){
cerr<<msg<<endl;
}
return 0;
}
C++标准中的异常:
详情见菜鸟教程https://www.runoob.com/cplusplus/cpp-exceptions-handling.html
动态内存
C++程序中的内存分为两个部分:
- 栈:在函数内部声明的所有变量都将占用栈内存。
- 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。
在C++中,使用特殊的运算符**
new
**为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。如果不需要动态分配内存空间,可以使用delete运算符,删除之前由new运算符分配的内存。
new和delete运算符
new data-type;
// data-type 可以是包括数组在内的任意内置的数据类型,也可以是包括类或结构在内的用户自定义的任何数据类型。
例:定义一个指向double类型指针,然后请求内存,该内存在执行时被分配。
double* pvalue = NULL; // 初始化为 null 的指针。 pvalue = new double; // 为变量请求内存。
如果自由存储区被用完,可能无法成功分配内存。所以建议检查new运算符是否返回NULL指针,并采取以下适当的操作:
double* pvalue = NULL; if(!(pvalue = new double)){ cout<<"Error: out of memory."<<endl; exit(1); }
**
malloc()
**函数:尽量不使用malloc()
函数。new 与malloc()函数相比,new不仅分配了内存,还创建了对象。使用delete释放内存
delete pvalue;
实例:
#include<iostream>
using namespace std;
int main(){
double *pvalue = NULL;
pvalue = new double;
*pvalue = 29494.99;
cout<<"Value of pvalue : "<<*pvalue<<endl;
delete pvalue;
return 0;
}
// 执行结果为:
// Value of pvalue : 29495
数组的动态内存分配
假设我们要为一个字符数组(一个有20个字符的字符串)分配内存,我们可以使用上面实例中的语法来为数组动态地分配内存,如下所示:
char* pvalue = NULL; pvalue = new char[20]; delete [] pvalue;
new操作符的通用语法,可以为多维数组分配内存,如下所示:
// 一维数组
// 动态分配,数组长度为m
int *array = new int[m];
// 释放内存
delete [] array;
// 二维数组
int **array;
// 假定第一维长度为m,第二维长度为n
// 动态分配空间
array = new int *[m];
for(int i = 0; i < m; i++){
array[i] = new int[n];
}
// 释放
for(int i = 0; i < m; i++){
delete [] array[i];
}
delete [] array;
二维数组实例测试:
#include <iostream>
using namespace std;
int main()
{
int **p;
int i,j; //p[4][8]
//开始分配4行8列的二维数据
p = new int *[4];
for(i=0;i<4;i++){
p[i]=new int [8];
}
for(i=0; i<4; i++){
for(j=0; j<8; j++){
p[i][j] = j*i;
}
}
//打印数据
for(i=0; i<4; i++){
for(j=0; j<8; j++)
{
if(j==0) cout<<endl;
cout<<p[i][j]<<"\t";
}
}
//开始释放申请的堆
for(i=0; i<4; i++){
delete [] p[i];
}
delete [] p;
return 0;
}
三维数组
int ***array; // 假定数组第一维为 m, 第二维为 n, 第三维为h // 动态分配空间 array = new int **[m]; for( int i=0; i<m; i++ ) { array[i] = new int *[n]; for( int j=0; j<n; j++ ) { array[i][j] = new int [h]; } } //释放 for( int i=0; i<m; i++ ) { for( int j=0; j<n; j++ ) { delete[] array[i][j]; } delete[] array[i]; } delete[] array;
实例:
#include <iostream> using namespace std; int main() { int i,j,k; // p[2][3][4] int ***p; p = new int **[2]; for(i=0; i<2; i++) { p[i]=new int *[3]; for(j=0; j<3; j++) p[i][j]=new int[4]; } //输出 p[i][j][k] 三维数据 for(i=0; i<2; i++) { for(j=0; j<3; j++) { for(k=0;k<4;k++) { p[i][j][k]=i+j+k; cout<<p[i][j][k]<<" "; } cout<<endl; } cout<<endl; } // 释放内存 for(i=0; i<2; i++) { for(j=0; j<3; j++) { delete [] p[i][j]; } } for(i=0; i<2; i++) { delete [] p[i]; } delete [] p; return 0; }
对象的动态内存分配
#include <iostream>
using namespace std;
class Box
{
public:
Box() {
cout << "调用构造函数!" <<endl;
}
~Box() {
cout << "调用析构函数!" <<endl;
}
};
int main( )
{
Box* myBoxArray = new Box[4];
delete [] myBoxArray; // 删除数组
return 0;
}
// 执行结果为:
// 调用构造函数!
// 调用构造函数!
// 调用构造函数!
// 调用构造函数!
// 调用析构函数!
// 调用析构函数!
// 调用析构函数!
// 调用析构函数!
C++命名空间
命名空间用于解决不同库中的同名函数问题。
定义
namespace namespace_name{
// 代码声明
}
调用带有命名空间的函数或变量
name::code; // code 可以是变量或函数
实例
#include<iostream>
using namespace std;
namespace first_space{
void func(){
cout<<"命名空间一......"<<endl;
}
}
namespace second_space{
void func(){
cout<<"命名空间二......"<<endl;
}
}
int main(){
first_space::func(); // 调用第一个命名空间
second_space::func(); // 调用第二个命名空间
return 0;
}
使用 using namespace
指令,在使用命名空间时就可以不用在前面加上命名空间的名称
实例:
using namespace first_space;
int main(){
func(); // 调用第一个命名空间内的函数
return 0;
}
不连续的命名空间
命名空间可以定义在几个不同的部分,可以由几个单独定义的部分组成。
一个命名空间的各个组成部分可以分散在多个文件中。
命名空间的定义可以是定义一个新的命名空间,也可以是为已有的命名空间增加新元素。
嵌套的命名空间
// 语法
namespace namespace_name1{
// 代码声明
namespace namespace_name2{
// 代码声明
}
}
// 访问
// 使用 :: 运算符访问嵌套的命名空间。
// 访问namespace_name2中的成员
using namespace namespace_name1::namespace_name2;
// 访问namespace_name1中的成员
using namespace namespace_name1;
C++模板
模板是泛型编程的基础,泛型编程是以一种独立于任何特定类型的方式编写代码。
模板是创建泛型或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子。
每个容器都有一个单一的定义,比如向量,我们可以定义许多不同类型,例:
vector<int> 或 vector<string>
函数模板
template <typename type> ret-type func-name(patameter list){
// 函数主体
}
// type 为函数所使用的数据类型的占位符名称
实例:
#include<iostream>
#include<string>
using namespace std;
template <typename T>
inline T const& Max (T const& a, T const& b){
return a < b ? b : a;
}
int main(){
int i = 39;
int j = 20;
cout<<"Max(i, j): "<<Max(i,j)<<endl; // 39
string s1 = "Hello";
string s2 = "world";
cout<<"Max(s1,s2): "<<Max(s1,s2)<<endl; // World
return 0;
}
类模板
// 定义
template <class type> class class-name{
//
}
// type 占位符类型名称
类模板没有自动类型推导,必须使用显示指定类型的方式。
实例:
#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>
using namespace std;
template <class T>
class Stack {
private:
vector<T> elems; // 元素
public:
void push(T const&); // 入栈
void pop(); // 出栈
T top() const; // 返回栈顶元素
bool empty() const{ // 如果为空则返回真。
return elems.empty();
}
};
template <class T>
void Stack<T>::push (T const& elem)
{
// 追加传入元素的副本
elems.push_back(elem);
}
template <class T>
void Stack<T>::pop ()
{
if (elems.empty()) {
throw out_of_range("Stack<>::pop(): empty stack");
}
// 删除最后一个元素
elems.pop_back();
}
template <class T>
T Stack<T>::top () const
{
if (elems.empty()) {
throw out_of_range("Stack<>::top(): empty stack");
}
// 返回最后一个元素的副本
return elems.back();
}
int main()
{
try {
Stack<int> intStack; // int 类型的栈
Stack<string> stringStack; // string 类型的栈
// 操作 int 类型的栈
intStack.push(7);
cout << intStack.top() <<endl;
// 操作 string 类型的栈
stringStack.push("hello");
cout << stringStack.top() << std::endl;
stringStack.pop();
stringStack.pop();
}
catch (exception const& ex) {
cerr << "Exception: " << ex.what() <<endl;
return -1;
}
}
C++预处理器
预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。
所有的预处理指令,都是以井号(
#
)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是C++语句,不用分号(
;
)结尾。
#define 预处理器
用于创建符号常量,该常量通常称为宏
#define macro-name replacement-text
例:
#include<iostream> using namespace std; #define PI 3.14159 int main(){ cout<<"value of PI : "<<PI<<endl; return 0; }
参数宏
可以使用 #define 定义一个带有参数的宏
例:
#include<iostream> using namespace std; #define MIN(a,b) (a<b?a:b) int main(){ int i, j; i = 100; j = 30; cout<<"较小的值为:"<<MIN(i,j)<<endl; return 0; }
条件编译
有几个指令可以用来有选择地对部分程序源代码进行编译。这个过程被称为条件编译。
示例:
#ifdef NULL #define NULL 0 #endif
调试开关可以使用一个宏来实现,
#ifdef DEBUG cerr<<"Variable x = "<<x<<endl; #endif
使用
#if 0
语句注释掉程序的一部分示例:
#if 0 不进行编译的代码 #endif
实例:
#include <iostream> using namespace std; #define DEBUG #define MIN(a,b) (((a)<(b)) ? a : b) int main () { int i, j; i = 100; j = 30; #ifdef DEBUG cerr <<"Trace: Inside main function" << endl; #endif #if 0 /* 这是注释部分 */ cout << MKSTR(HELLO C++) << endl; #endif cout <<"The minimum is " << MIN(i, j) << endl; #ifdef DEBUG cerr <<"Trace: Coming out of main function" << endl; #endif return 0; } // 运行结果 Trace: Inside main function The minimum is 30 Trace: Coming out of main function
#
和 ##
运算符
#
运算符把令牌转换为用引号引起来的字符串#define CHUAN(x) #x int main(){ cout<<CHUAN(abcd)<<endl; // 将 abcd=>"abcd" return 0; }
##
运算符用于连接两个令牌#define CONCAT(x,y) x ## y
实例:
#include<iostream> using namespace std; #define concat(a,b) a ## b int main(){ int xy = 100; cout<<concat(x,y)<<endl; // 100 return 0; } // 工作原理 cout<<concat(x,y); // 转换为了: cout<<xy;
C++中的预定义宏
__LINE__
:在程序编译时包含当前行号。
__FILE__
:这会在程序编译时包含当前文件名
__DATE__
:包含一个形式为month/day/year
的字符串,表示把源文件转换为目标代码的日期。
__TIME__
:包含一个形式为hour:minute:second
的字符串,表示程序被编译的时间。实例
#include<iostream> using namespace std; int main() { cout << __LINE__ << endl; cout << __FILE__ << endl; cout << __DATE__ << endl; cout << __TIME__ << endl; return 0; } // 结果为 5 test.cpp May 8 2023 10:50:26
C++信号处理
C++多线程
多线程时多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序。
一般情况下,两种类型的多任务处理:基于进程和基于线程。
- 基于进程:程序的并发执行
- 基于线程:同一程序的片段并发执行
创建线程
// 创建一个POSIX线程
#include<pthread.h>
pthread_create (thread, attr, start_routing, arg)
// thread:指向线程标识符指针
// attr:一个不透明的属性对象,可以被用来设置线程属性。可以指定线程属性对象,也可以使用默认值NULL
// start_routine:线程运行函数起始地址,一旦线程被创建就会执行。
// arg:运行函数的参数。它必须通过把引用作为指针强制转换为void类型进行传递。如果没有传递参数,则使用NULL。
;
// 创建线程成功时,函数返回 0 ,
// 若返回值不为 0 ,则说明创建线程失败。
终止线程
#include<pthread.h>
pthread_exit (status)
实例:
#include<iostream>
#include<pthread.h>
using namespace std;
#define NUM_THREADS 5
// 线程运行函数
void* say_hello(void* args){
cout<<"hello yan"<<endl;
return 0;
}
int main(){
// 定义线程的id变量,多个变量使用数组
pthread_t tids[NUM_THREADS];
for(int i=0;i<NUM_THREADS;i++){
//参数依次是:创建线程的id,线程参数,调用的函数,传入函数的参数
int ret = pthread_create(&tids[i],NULL,say_hello,NULL);
if(ret != 0){
cout<<"pthread_cread error_code="<<ret<<endl;
}
}
pthread_exit(NULL);
}
…
C++Web编程
---------- C++资源库 ----------
C++ STL教程
组件 | 描述 |
---|---|
容器(Containers) | 容器是用来管理某一类对象的集合。C++提供了各种不同类型的容器,比如:deque、list、vector、map等。 |
算法(Algorithms) | 算法作用于容器。它们提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换等操作。 |
迭代器(iterators) | 迭代器用于遍历对象集合的元素。这些集合可能是容器,也可能是容器的子集。 |
实例:向量容器
#include<iostream>
#include<vector>
using namespace std;
int main(){
vector<int> vec;
int i;
cout<<"vector size="<< vec.size()<<endl;
for(i = 0;i<5;i++){
vec.push_back(i);
}
cout<<"extended vector size="<<vec.size()<<endl;
for(i=0;i<5;i++){
cout<<"value of vec["<< i << "]=" << vec[i] << endl;
}
// 使用迭代器iterator 访问值
vector<int>::iterator v = vec.begin();
while(v!= vec.end()){
cout<<"value of v="<<*v<<endl;
v++;
}
return 0;
}
- push_back()成员函数在向量的末尾插入值,如果有必要会扩展向量的大小。
- size() 函数显示向量的大小。
- begin() 函数返回一个指向向量开头的迭代器。
- end() 函数返回一个指向向量末尾的迭代器。
---------- 拓展 ----------
函数
-
setw()
语法:
setw(n)
概念:
用于设置字段的宽度,n表示宽度,用数字表示,
只对紧接着的输出产生作用
。当后面紧跟着的输出字段长度小于 n 时,在该字段前面用空格补齐,
当输出字段长度大于 n 时,全部整体输出。
示例:
cout<<setw(10)<<"c++"<<endl; cout<<setw(10)<<"learnC++"<<endl; cout<<setfill('*')<<setw(10)<<"abcd"<<endl;
结果:
c++ learnC++ ******abcd
setfill()
设置字符填充。
使用setw()函数和setfill()函数时,需要使用头文件<iomanip>
-
strcpy(s1, s2) —— 使用方法报错时,使用
strcpy_s
复制字符串
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
第一次出现的位置。 -
getline()
从外部读一行。
cin
函数的附加函数 -
ignore()
忽略掉之前读语句留下的多余字符。
cin
函数的附加函数
一些重要知识
const详解
const
是constant
的缩写,意为不变的、不易改变的。在C++中用来修饰内置类型变量,自定义对象、成员函数、返回值、函数参数。
一、const修饰普通类型的变量
示例:
const int a = 7;
int b = a; // 正确
a = 8; // 错误,不能改变
解释:
变量 a 被 const 修饰后,定义为了一个常量,所以常量 a 不能被重新赋值。
实例:
#include<iostream>
using namespace std;
int main(){
const int a = 7;
int* p = (int*)&a;// 强制转换为指针类型,将a的地址转换为指针类型赋值给指针p
// 不能写为 int* p = &a;
// 因为const int* 类型的值不能用于初始化 int* 类型的实体
*p = 8;
cout<<a; // 7
return 0;
}
关键字
volatile
意为易变的,容易改变的。当使用了该关键字后,不会再认定 a 为常量。
实例:
volatile const int a = 7;
int *p = (int*)&a;
*p = 8;
cout<<a;// 8
二、const修饰指针变量
const 修饰指针变量的三种情况
const修饰指针指向的内容,则内容为不可变量
const int *p = 8;
指针指向的内容 8 不可改变,为
左定值
,const位于*
的左边。const修饰指针,则指针为不可变量
int a = 8; int* const p = &a; *p = 9;// 正确 int b = 7; p = &b;// 错误
const指针p指向的内存地址不能够被改变,内容可以改变。简称
右定向
,因为const位于*
的右边。const修饰指针和指针指向的内容,则指针和指针指向的内容都为不可变量。