本贴为复习专用
已更新到C++11的lambda表达式
IDE编程步骤
①源代码:你手敲的代码
②编译器:用来编译源代码, 相当于翻译,把你的C++语言C语言翻译给电脑听,即对电脑讲机器语言,当然也是代码,这里把翻译过的代码叫目标代码,这一步也处理了预处理器包含的指令
③目标代码:被编译器编译过的源代码
④链接程序=启动代码+库代码+目标代码, 你的程序可能需要调用很多其他的代码来帮助你完成任务,所以需要把他们链接起来, 整合起来的代码就是可执行代码
⑤可执行代码, 最终代码即程序运行时的代码
文本编辑器包括 (Windows Notepad)、(OS Edit command)、(Brief)、(Epsilon)、(EMACS) 和( vim/vi)
文本编辑器的名称和版本在不同的操作系统上可能会有所不同。例如,(Notepad )通常用于 Windows 操作系统上,(vim/vi )可用于 Windows 和 Linux/UNIX 操作系统上。
C++ 程序的源文件通常使用扩展名( .cpp、.cp 或 .c), 大多数的 C++ 编译器并不在乎源文件的扩展名,但是如果您未指定扩展名,则默认使用 .cpp。
写在源文件中的源代码是人类可读的源。它需要"编译",转为机器语言,这样 CPU 可以按给定指令执行程序。
C++ 编译器用于把源代码编译成最终的可执行程序。
最常用的免费可用的编译器是 GNU 的 C/C++ 编译器,
如果您使用的是 Linux 或 UNIX,请在命令行使用下面的命令来检查您的系统上是否安装了 GCC:
如果您使用的是 Mac OS X,最快捷的获取 GCC 的方法是从苹果的网站上下载 Xcode 开发环境,并按照安装说明进行安装。一旦安装上 Xcode,您就能使用 GNU 编译器。
为了在 Windows 上安装 GCC,您需要安装 MinGW。为了安装 MinGW,请访问 MinGW 的主页 www.mingw.org,进入 MinGW 下载页面,下载最新版本的 MinGW 安装程序,命名格式为 MinGW-.exe。当安装 MinGW 时,您至少要安装 gcc-core、gcc-g++、binutils 和 MinGW runtime,但是一般情况下都会安装更多其他的项。添加您安装的 MinGW 的 bin 子目录到您的 PATH 环境变量中,这样您就可以在命令行中通过简单的名称来指定这些工具。当完成安装时,您可以从 Windows 命令行上运行 gcc、g++、ar、ranlib、dlltool 和其他一些 GNU 工具。
C++的扩展名往往是.cpp 在linux中往往是.cxx,目标代码文件是.o 可执行文件是.out
类是一种规范, 描述了你设计的新型数据结构, 对象就是类的实现,即根据这种规范所构造的数据结构, 换句话说,类描述了:(一种数据类型的全部属性包括了可使用它执行的操作), 而对象就是根据这些描述来创建的实体
函数可以来自函数库, 类也有类库,ostream和istream系列类就是一种类库,还有fstream系列类,类库不好听我们更喜欢叫系列类
预处理器
什么是预处理器呢?在主编译之前对源文件进行处理,你不需要进行特殊的操作来调用,只需要#即可
使用#Include编译指令,比如#include 就是把iostream文件添加到程序中,这是一种比较典型的预处理器操作,在源代码被编译之前,替换或添加文本
#include指令会把iostream文件中的内容和源代码一起发送给编译器,具体就是iostream中的全部文件会取代#include 这一行代码,并且不修改源代码文件,而是新生成一个复合文件
头文件=包含文件
iostream这种文件叫包含文件(常常被include包含在其他文件中) ,也叫头文件(常常在程序起始处)
C++有很多头文件,每个头文件都带来了一组特定的工具
C的头文件往往是.h扩展名, C++往往是没有扩展名,使用C的头文件时,你需要(去掉.h且在名前加c即cmath=math.h)
C用不同扩展名表示不同文件类型,C++取消所有扩展名
在应用程序中引用非标准库时,可以先去浏览一下这些包含文件,这是最好的文档信息,你可以掌握很多知识,这种行为是很好的习惯
名称空间
上述头文件去掉了.h其实不是这么简单,而是让没有.h的头文件包含了名称空间
名称空间编译指令using namespace 比如using namespace std; 这里是using编译指令
打断一下,你现在了解了两个编译指令了,一个是using一个是#include
头文件包含了名称空间, 名称空间包含了(所需要的类,函数,变量等的名字),想访问他们有如下两种方式以std为例
using std::cout; //通过using编译指令来声明了cout的来源是std名称空间
using namespace std; cout<<'a'<
cout预定义的对象,它懂得如何显示字符串,数字和字符等数据
cout是个对象,那是哪个类的对象呢????
插入运算符<<符号可以把信息流入到cout中即cout<<'a'
cout<<'a'就是把字符a从程序中流出去了, 而cout正是表示了这种输出流,cout对象的具体属性是在iostream头文件中定义的,属性包括了一个插入运算符<<, 可以把数据插入到输出流中
控制符endl,表示重启一行,如果把控制符endl也插入到输出流中,那你屏幕光标将会(直接跳到下一行的开头),endl控制符也是iostream头文件中定义的,同样名字也在std名称空间中
cout代表了输出流,所以并不能移动光标
cout是指代表了输出流,所以也不能产生空格,如果你需要空格,那就把空格插入到输出流中吧
C++程序可以看成一组函数, 而每个函数又是一组语句
声明语句创建了变量,作用就是:指出了(信息存储的具体位置)和(需要的内存空间大小)
(计算机存储数据时,必须要知道数据的存储位置和所占内存空间大小)
声明语句提供了(存储类型)和(位置标签),位置标签表示了具体位置,存储类型则代表了所占内存空间的大小信息
比如int num; int是整数类型,根据计算机系统不同,整数类型所占的内存空间(位数或字节数)也不同,而num则代表了上述内存空间的位置标签,num作为变量是可以改变的,就是内存空间存储的信息是可以修改的
不同的数据类型,则代表了不同的内存空间占用的大小数量
你不需要管这些底层的事情,编译器会帮你分配好内存并标记好位置
定义和声明最好根据函数和变量来分别讨论
对于变量而言:
变量的声明,指出存储位置和所需空间大小
变量的定义,指出存储位置和所需空间大小,且申请相应内存空间
对于函数而言:
函数的声明,相当于变量声明于变量,声明函数原型
函数的定义,给出函数的结构体
思考:声明、定义、引用三者各什么意思?是否有什么联系??
赋值语句=赋值运算符则可以对内存空间中存储的数据进行修改
代表输入流的cin对象,插入运算符>>为cin提供了流入方向
cout和cin都是智能对象,可以通过输入对象的类型而自动调整输入的数据类型
换行符\n, 换行符常用在字符串中,其实\n是一个字符,名称是换行符而已,
换行符\n和控制符endl的区别
显然是字符数量少了\n两个endl四个
endl能够保证在程序继续运行前刷新屏幕,也就是立即显示在屏幕上,而\n不能, 甚至有时候你要在输入信息后才会有提示
当然如果你只想要一个空行,两者都可以选择
cout<
cout<<"\n";
由于;标志着一条语句的结束, 所以你在一条语句中使用多少回车都是无法表示该语句的结束的,甚至是没有什么影响,所以在一条语句中,回车的作用=空格=制表符(多个空格)
这并不意味着你可以随意使用多个空格或者回车, 无论是C还是C++你都不能把空格,制表符,回车,放到元素中(比如变量名), 也不能放到字符串中,这很重要,cout<<"字符串中不能添加回车,不要换行"
标记和空白
一行代码中不可分割元素比如int 比如main等,都叫做标记,通常我们用空格制表符回车把两个标记分开({}就是用回车分开的)
有些可以通过一个空格来分开,有些不可以,比如 int main()你不要把回车插入到int和main中间
分号是语句结束符。也就是说,每个语句必须以分号结束。它表明一个逻辑实体的结束。
C++ 标识符是用来标识变量、函数、类、模块,或任何其他用户自定义项目的名称。一个标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)。
C++ 注释以 /* 开始,以 */ 终止。注释也能以 // 开始,直到行末为止。
类型 | 位 | 范围 |
---|---|---|
char | 1 个字节 | -128 到 127 或者 0 到 255 |
unsigned char | 1 个字节 | 0 到 255 |
signed char | 1 个字节 | -128 到 127 |
int | 4 个字节 | -2147483648 到 2147483647 |
unsigned int | 4 个字节 | 0 到 4294967295 |
signed int | 4 个字节 | -2147483648 到 2147483647 |
short int | 2 个字节 | -32768 到 32767 |
unsigned short int | 2 个字节 | 0 到 65,535 |
signed short int | 2 个字节 | -32768 到 32767 |
long int | 8 个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
signed long int | 8 个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
unsigned long int | 8 个字节 | 0 to 18,446,744,073,709,551,615 |
float | 4 个字节 | +/- 3.4e +/- 38 (~7 个数字) |
double | 8 个字节 | +/- 1.7e +/- 308 (~15 个数字) |
long double | 16 个字节 | +/- 1.7e +/- 308 (~15 个数字) |
wchar_t | 2 或 4 个字节 | 1 个宽字符 |
unsigned或signed改变范围不改变字节数, short 字节 *0.5, long字节*2,如short int 则字节为2, long int 字节为8
使用 (typedef) 为(一个已有的类型取)一个新的名字
使用 (sizeof()) 函数来获取各种数据类型的大小。
运算符sizeof返回类型或变量长度,单位是B字节
对变量操作时, 无需加括号,即int a = 0 ; cout<
对数据类型操作时,加括号,即cout<
sizeof运算符直接指出了整个数组的长度(有多少个元素就有多长)
sizeof运算符对指针进行操作时,得到的是指针的长度,即使指针指向的是个数组
strlen()函数则返回可见字符的个数,不包括'\0'空字符
枚举
枚举类型(enumeration)是C++中的一种派生数据类型,它是由用户定义的若干枚举常量的集合.如果一个变量只有几种可能的值,可以定义为枚举(enumeration)类型。所谓"枚举"是指将变量的值一一列举出来,变量的值只能在列举出来的值的范围内。
创建枚举,需要使用关键字 enum。
默认情况下,第一个名称的值为 0,第二个名称的值为 1,第三个名称的值为 2,以此类推。但是,您也可以给名称赋予一个特殊的值,只需要添加一个初始值即可。例如,在下面的枚举中,green 的值为 5。
在这里,blue 的值为 6,因为默认情况下,每个名称都会比它前面一个名称大 1,但 red 的值依然为 0。
代码中是两种不同的创建枚举的方法,第一种方法,name, age,width,height的值,是其下标值,如age下表是1,则age=1;
第二种方法,直接赋值,age就是18
注意,枚举的各个元素都是符号常量,并且指定的具体数值必须是整数,当然第一种情况默认赋值必然是整数
/*
enum 枚举名{
标识符[=整型常数],
标识符[=整型常数],
...
标识符[=整型常数]
} 枚举变量;
*/
enum color { red, green, blue} c ;
c = blue;
enum color { red, green = 5, blue};
enum person {name, age, width, height};
enum person0 {name = 1, age = 18, width =120, height=133232};
类型 | 描述 |
---|---|
bool | 存储值 true 或 false。 |
char | 通常是一个字符(八位)。这是一个整数类型。 |
int | 对机器而言,整数的最自然的大小。 |
float | 单精度浮点值。单精度是这样的格式,1位符号,8位指数,23位小数。 |
double | 双精度浮点值。双精度是1位符号,11位指数,52位小数。 |
void | 表示类型的缺失。 |
wchar_t | 宽字符类型。 |
使用( extern 关键字)在任何地方声明一个变量
强制类型转换(这里是简单地类型转换,还有更高级的操作在fstream类中)
static_cast<>运算符是强制类型转换符
static_cast (xjh)把xjh变量强制转换成int类型
// 变量声明
extern int a, b;
extern int c;
extern float f;
// 函数声明
int func();
int main ()
{
// 变量定义
int a, b;
int c;
float f;
// 实际初始化
a = 10;
b = 20;
c = a + b;
cout << c << endl ;
f = 70.0/3.0;
cout << f << endl ;
// 函数调用
int i = func();
return 0;
}
// 函数定义
int func()
{
return 0;
}
如果只声明不定义会怎么样呢?
#include
extern int a;
int main(void)
{
extern int b;
int a =0;
b=1;
std::cout<<"a:"<>>g++ main.cpp
/tmp/cctOXSfB.o: In function `main':
main.cpp:(.text+0x11): undefined reference to `b'
main.cpp:(.text+0x50): undefined reference to `b'
collect2: error: ld returned 1 exit status
在 C++ 中,有两种简单的定义常量的方式:
预处理编译指令#define这里举个例子来说明
#define INT_MAX 32767
编译指令#define在编译前在程序中查找所有的INT_MAX,然后用32767替换。完成替换后,程序会被编译
当然预处理器仅仅替换独立的标记,而不会替换嵌入INT_MAX的标记,如PINT_MAX或UINT_MAX
整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制。
#include
using namespace std;
#define LENGTH 10
#define WIDTH 5
#define NEWLINE '\n'
int main()
{
int area;
area = LENGTH * WIDTH;
cout << area;
cout << NEWLINE;
const int NUMBER = 10;
const float PRICE= 13.23;
double output = NUMBER * PRICE;
cout << output;
return 0;
}
/*
212 // 合法的
215u // 合法的
0xFeeL // 合法的
078 // 非法的:8 不是八进制的数字
032UU // 非法的:不能重复后缀
85 // 十进制
0213 // 八进制
0x4b // 十六进制
30 // 整数
30u // 无符号整数
30l // 长整数
30ul // 无符号长整数
*/
3.14159 // 合法的
314159E-5L // 合法的
510E // 非法的:不完整的指数
210f // 非法的:没有小数或指数
.e55 // 非法的:缺少整数或分数
转义序列 | 含义 |
---|---|
\ | \ 字符 |
' | ' 字符 |
" | " 字符 |
? | ? 字符 |
\a | 警报铃声 |
\b | 退格键 back |
\f | 换页符 form feed 换页 |
\n | 换行符 newline换行 |
\r | 回车 return |
\t | 水平制表符 |
\v | 垂直制表符 |
\ooo | 一到三位的八进制数 |
\xhh . . . | 一个或多个数字的十六进制数 |
引用和指针的三个不同:
不存在空引用。引用必须连接到一块合法的内存。(不存在空引用,存在空指针)
引用时要注意两个两个变量的类型要求保持一致,const引用const int类型将产生错误
#include
using namespace std;
int main ()
{
// 声明简单的变量
int i;
double d;
// 声明引用变量
int& r = i;
double& s = d;
i = 5;
cout << "Value of i : " << i << endl;
cout << "Value of i reference : " << r << endl;
d = 11.7;
cout << "Value of d : " << d << endl;
cout << "Value of d reference : " << s << endl;
return 0;
}
存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。说明符放置在它们所修饰的类型之前
auto 存储类, auto 关键字用于两种情况:声明变量时根据初始化表达式自动推断:该变量的类型、声明函数时函数返回值的占位符。
可以使用STL中的 typeid(auto_name).name()来查看类型名
auto f=3.14; //double
auto s("hello"); //const char*
auto z = new auto(9); // int*
auto x1 = 5, x2 = 5.0, x3='r';//错误,必须是初始化为同一
cout<
register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 '&' 运算符(因为它没有内存位置)。
寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义 'register' 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。
static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。
当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。
当 static 用在类数据成员上时,会导致仅有一个该成员的副本被类的所有对象共享。
extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。
当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。
extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候,
//File0
#include
int count ;
extern void write_extern();
int main()
{
count = 5;
write_extern();
}
//File1
#include
extern int count;
void write_extern(void)
{
std::cout << "Count is " << count << std::endl;
}
运算符 | 描述 | 实例 |
---|---|---|
+ | 把两个操作数相加 | A + B 将得到 30 |
- | 从第一个操作数中减去第二个操作数 | A - B 将得到 -10 |
* | 把两个操作数相乘 | A * B 将得到 200 |
/ | 分子除以分母 | B / A 将得到 2 |
% | 取模运算符,整除后的余数 | B % A 将得到 0 |
++ | 自增运算符,整数值增加 1 |
A++ 将得到 11 |
-- | 自减运算符,整数值减少 1 |
A-- 将得到 9 |
&& | 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 |
(A && B) 为假。 |
|| | 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 |
(A || B) 为真。 |
! | 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 |
!(A && B) 为真。 |
sizeof | sizeof 运算符返回变量的大小。例如,sizeof(a) 将返回 4,其中 a 是整数。 |
|
Condition ? X : Y | 条件运算符。如果 Condition 为真 ? 则值为 X : 否则值为 Y。 |
|
, | 逗号运算符会顺序执行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最后一个表达式的值。 |
|
.(点)和 ->(箭头) | 成员运算符用于引用类、结构和共用体的成员。 |
|
Cast | 强制转换运算符把一种数据类型转换为另一种数据类型。例如,int(2.2000) 将返回 2。 |
|
& | 指针运算符 & 返回变量的地址。例如 &a; 将给出变量的实际地址。 |
|
* | 指针运算符 * 指向一个变量。例如,*var; 将指向变量 var。 |
|
对于++和--,只要用对象,就进行自加自减,比如
int a = 0; int b = 0; a++; cout<
? : 运算符 可以用来替代 if...else 语句。
Exp1 ? Exp2 : Exp3;
如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。
形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。
当调用函数时,有两种向函数传递参数的方式:
传值调用 | 该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数对实际参数没有影响。 |
指针调用 | 该方法把参数的地址复制给形式参数。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。 |
引用调用 | 该方法把参数的引用复制给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。 |
C++ 内置了丰富的数学函数,可对各种数字进行运算。下表列出了 C++ 中一些有用的内置的数学函数。为了利用这些函数,您需要引用数学头文件
序号 | 函数 & 描述 |
---|---|
1 | double cos(double); 该函数返回弧度角(double 型)的余弦。 |
2 | double sin(double); 该函数返回弧度角(double 型)的正弦。 |
3 | double tan(double); 该函数返回弧度角(double 型)的正切。 |
4 | double log(double); 该函数返回参数的自然对数。 |
5 | double pow(double, double); 假设第一个参数为 x,第二个参数为 y,则该函数返回 x 的 y 次方。 |
6 | double hypot(double, double); 该函数返回两个参数的平方总和的平方根,也就是说,参数为一个直角三角形的两个直角边,函数会返回斜边的长度。 |
7 | double sqrt(double); 该函数返回参数的平方根。 |
8 | int abs(int); 该函数返回整数的绝对值。 |
9 | double fabs(double); 该函数返回任意一个浮点数的绝对值。 |
10 | double floor(double); 该函数返回一个小于或等于传入参数的最大整数。 |
#include
#include
using namespace std;
int main ()
{
// 数字定义
short s = 10;
int i = -1000;
long l = 100000;
float f = 230.47;
double d = 200.374;
// 数学运算
cout << "sin(d) :" << sin(d) << endl;
cout << "abs(i) :" << abs(i) << endl;
cout << "floor(d) :" << floor(d) << endl;
cout << "sqrt(f) :" << sqrt(f) << endl;
cout << "pow( d, 2) :" << pow(d, 2) << endl;
return 0;
}
随机数生成器,有两个相关的函数。一个是 rand(),该函数只返回一个伪随机数。生成随机数之前必须先调用 srand() 函数。
下面是一个关于生成随机数的简单实例。实例中使用了 time() 函数来获取系统时间的秒数,通过调用 rand() 函数来生成随机数:注意需要包含的库文件
#include
#include
#include
using namespace std;
int main ()
{
int i,j;
// 设置种子
srand( (unsigned)time( NULL ) );
/* 生成 10 个随机数 */
for( i = 0; i < 10; i++ )
{
// 生成实际的随机数
j= rand();
cout <<"随机数: " << j << endl;
}
return 0;
}
如何数组声明?
typeName arrayName[arraySize];
其中typeName是你元素类型, arrayName是你的自定义数组名,arraySize是你自定义数组长度(不能是变量必须是const)即元素总个数
举例说明数组的复合意义?float xjh[1228]; 这里的xjh类型不是数组,而是float数组, 要这么说:"你要建立一个int数组或者float数组"
数组从0开始索引并用[下标]来索引
数组赋值也不被允许,即int xjh[3] = {1,2,2,8};int xjh1[3] = {1,2,2,8};xjh1 = xjh;这种操作不被允许,只能通过下标访问元素来对某个元素进行赋值,修改等
所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素。
声明数组,需要指定元素的类型和元素的数量,type arrayName [ arraySize ];
这叫做一维数组。arraySize 必须是一个大于零的整数常量,type 可以是任意有效的 C++ 数据类型。
初始化数组
可以逐个初始化数组,也可以使用一个初始化语句
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
大括号 { } 之间的值的数目不能大于我们在数组声明时在方括号 [ ] 中指定的元素数目
如果您省略掉了数组的大小,数组的大小则为初始化时元素的个数。
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};
快速初始化,举例如int数组 int xjh[3] = {1,2,2,8}; 即利用{}来对元素对位初始化
注意{}只能在初始化时使用,若int xjh[3]; 然后再xjh[3] = {1,2,2,8};则不被允许
数组元素可以通过数组名称加索引进行访问。元素的索引是放在方括号内,跟在数组名称的后边。
数组赋值,只能元素对元素,不能数组名对数组名,如下操作是非法的
int array[3] = {1,2,3};
int array1 = array;
int data[3][4] = {{1,2,3,4}, {5,6,7,8}, {1,2,3,4}};
int (*data)[4];
先看data,显然我们知道data是个数组名,也是个指针,常规的int data[];是说data是个int数组,data也是个指向int类型的指针
这里的data是什么呢? data是 一个指针,这个指针指向的是个数组,这个数组是由4个int类型元素构成的,即int (*data) [4] ;这样太晦涩难懂了, 也可以这样int data[][4];意义完全相同,这里都表明了data不是数组而是指针
思路:
1⃣️int data[][3];可以看成int (*data)[3],即data是个指针,指向一个包含了3个int的数组
2⃣️指针完全可以用作数组名,例如int* ptr; ptr[1];,那么data则可以当做数组名,从而int data[][3];
这里明白了,那么如何传递数组的长度呢?
显然int data[][4]对行数是没有限制的,只是明确了列数要是4,即data指针指向的每个数组长度是4
同样的,函数对C风格字符串没有办法传递和返回,我们直接用string更合适
函数指针,函数指针这里交给指针笔记记录
二维数组和指向数组的指针
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
char greeting[] = "Hello";
//上述两个语句是一样的效果
序号 | 函数 & 目的 |
---|---|
1 | strcpy(s1, s2); 复制字符串 s2 到字符串 s1。 |
2 | strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾。 |
3 | strlen(s1); 返回字符串 s1 的可见长度。(不包含NULL字符,即'\0') |
4 | strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1s2 则返回值大于 0。 |
5 | strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。 |
6 | strstr(s1, s2); 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。 |
7 | sizeof(s1);返回整个数组的长度,包括任何字符 |
注意区分cin.get()和getline(),前者是类方法,后者是函数
get与getline区别
char arr[100];
cout<<"输入一段文字:"<
get与getline有两个参数,第一个参数是用来存储的数组名称,第二个参数代表着读取的字节数。
输入:asdfg 回车
get:只读取asdfg不读取回车,会导致下一个读取输入时第一个读取“回车”。
getline:getline读取asdfg回车,并将回车转换为“\0”读取,所以最终读取的是“asdfg\0”输入序列中无回车,下一个读取将会正常读取。
字符串输入,cin对象的解释
cin对象通过使用空白,即空格,制表符(多个空格)换行符, 来确定字符串的结束位置, 这说明cin对象在输入流中每次只读取一个单词, 读取单词后就把它放到数组中并自动添加空字符
首先你要包含string类库文件(头文件), string类的名称在标准名称空间std中,std::string即可完成引用
String类的对象来存储字符串,String类的设计让程序可以自动处理字符串的大小
数组不能赋值,但是String类对象可以,string s1;string s2; s1=s2;是允许的
还可以通过+来进行拼接合并,这里是无缝合并
通过类方法.size()可以快速获得String类对象的长度
在学习指针时,首先要知道()的优先级高于 [] , 且 [] 的优先级高于*
明确这一点,可以对包含指针的语句分析时,抽丝剥茧,快速理解复杂的语句。
A.B 直接成员运算符 |
A为对象或者结构体,访问A的成员B,若A是指针则不适用 |
A->B间接成员运算符 | A为指针,->是成员提取,A->B是提取A中的成员B,且A只能是指向类,结构的指针 |
:: | 作用域运算符,A::B表示作用域A中的名称B, A是命名空间,类,结构 |
: | 表示继承 |
:: | 作用域解析 | 作用域解析最优先 小中括号是二三 成员访问后增减 地址后边解引用 |
() | 小括号 | |
[] | 中括号,数组 | |
-> | 成员访问 | |
++ | 后缀自增 | |
-- | ||
! | 非 | |
~ | 位非 | |
+ | 正,加 | |
- | ||
++ | 前缀自增 | |
-- | ||
& | 地址 | |
* | 解除引用(指针) | |
double * p; //定义声明了一个指向double类型的指针p
double* p; //定义声明了一个指向double类型的指针p,强调p是指向double类型的指针变量
double *p; //定义声明了一个指向double类型的指针p,强调*p是double类型
内存分配
栈区(stack):栈区的空间由操作系统自动分配和释放,该部分主要用于存放函数的参数值、局部变量等,比如声明在函数中的一个局部变量int b,系统就会自动在栈中为b开辟空间。栈区的操作方式类似于数据结构中栈(这也许是造成许多人混淆的地方,正如有的人所说,我们可以把数据结构的栈理解成某个技术,而内存的栈正好用到了该技术,但二者其实并不一样)。另外需要注意的是,在windows下,栈是向低地址扩展的数据机构,是一块连续的内存区域。也就是说栈顶的地址和栈的最大容量是系统预先设定好的,比如在windows下,栈的大小为2MB(也有的说是1MB,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间,将会提示overflow。总之就是说栈的空间是有限的。
堆区(heap):堆区空间一般由程序员分配和释放(需要注意堆区与数据结构中的堆是两回事,其分配方式类似于链表),比如,在C中用malloc函数--- p1 = (char*) malloc(10); 在C++中用new,都将分配堆区空间。不过要注意p1本身是在栈中。
静态数据区:全局变量和静态变量都存放于该区。初始化的全局变量和初始化的静态变量放在一块区域,未初始化的全局变量和未初始化的静态变量放在相邻的另一区域。程序结束后由系统自动释放。
代码区:该区用于存放函数体的二进制代码。
数组名是一个常量指针
牢记下边两个恒等式 arr为数组名,p为数组名地址值赋值的指针, p=arr
左边是数组表示法,右边是指针表示法
arr[i] == *(p+i)
&arr[i] == p + i
#include
using namespace std;
const int MAX = 3;
int main ()
{
int var[MAX] = {10, 100, 200};
int *ptr;
ptr = var;
for (int i = 0; i < MAX; i++)
{
*var = i; // 这是正确的语法
var++; // 这是不正确的
ptr++; //TRUE, 指针加1个int单位字节,从而指向了下一个元素
}
return 0;
}
double arr[100];
double *pa = arr;
double *pb = &arr[0];
double *pbb = &arr;
double (*pc)[100];
double *pd[100];
#include
using namespace std;
int main()
{
int arr[3] = {0,1,2};
cout<
int main(){
double * ptr = new double;
ptr[1] = 100.21;
cout<<*(ptr+1)<
using namespace std;
const int MAX = 3;
int main ()
{
int var[MAX] = {10, 100, 200};
int *ptr;
// 指针中最后一个元素的地址
ptr = &var[MAX-1];
for (int i = MAX; i > 0; i--)
{
cout << "Address of var[" << i << "] = ";
cout << ptr << endl;
cout << "Value of var[" << i << "] = ";
cout << *ptr << endl;
// 移动到下一个位置
ptr--;
}
return 0;
}
Address of var[3] = 0xbfdb70f8
Value of var[3] = 200
Address of var[2] = 0xbfdb70f4
Value of var[2] = 100
Address of var[1] = 0xbfdb70f0
Value of var[1] = 10
#include
using namespace std;
int main()
{
int max = 3;
int* ptr = new int[max];
*ptr = 5;
ptr[1] = 11;
ptr[2] = 12;
for(int i=0;i<3;i++)
{
cout<<"ptr["<
辨析:
short (*pas)[20];
short *pas[20];
int *ar2[4];
int (*ar2)[4];
//解析
//牢记优先级() [] *
short (*pas)[20]; //pas是指针,指向由20个short元素组成的数组的指针
short *pas[20]; //pas是个数组,包含了20个指向short类型指针的数组
int *ar2 [4]; //ar2是个数组,表示由4个int指针组成的数组
//-1-参考int arr[4];表示arr是个int数组,长度为4
//-2-int* ar2[4];表示ar2是个int*数组,长度为4,只不过元素都是只想int的指针
int (*ar2) [4];//ar2是个指针,指向一个包含了4个int的数组
//-1-先看括号里面,(*ar2),表示ar2是个指针
//-2-再看括号外面,int ()[4];表示名为()的int数组,长度为4
// NO.1 right
int age = 30;
const int * p = &age; //无法通过p修改age,age自身可修改
// NO.2 right
const int age = 30;
const int * ptr = &age; //无法通过ptr修改age,age自身不可修改
// NO.3 false
const int age = 30;
int *ptr = &age; //可通过ptr修改age,age自身不可修改,矛盾,ERROR
#include
using namespace std;
int main() {
/*当对象是变量或者常量时,思考const的存在或不同位置,能否导致指针自身和指针指向的对象 改变?*/
int num = 10; //当对象是int变量
int test = 1;
int * num_ptr0 = # // TRUE, *num_ptr0,可修改,num_ptr0可修改,num可修改
const int * num_ptr1 = # // TRUE, *num_ptr1常量不可修改,num_ptr1可修改,num可修改
int * const num_ptr2 = # // TRUE, *num_ptr2可以修改, num_ptr2不可修改,num可修改
const int* const num_ptr3 = # // TRUE, *num_ptr3常量不可修改, num_ptr3常量不可修改,num可修改
const int price = 100; //当对象是int常量
int * price_ptr0 = &price; //ERROR, 指针指向类型和指向对象类型不匹配, nt类型的实体不能用const int来初始化,反之可以
const int *price_ptr1 = &price; //TRUE,
int * const price_ptr2 = &price; //ERROR, 指针指向类型和指向对象类型不匹配, int类型的实体不能用const int来初始化,反之可以
const int* const price_ptr3 = &price; //TRUE
return 0;
}
#include
using namespace std;
int main ()
{
int *ptr = NULL;
cout << "ptr 的值是 " << ptr ;
return 0;
}
ptr 的值是 0
if(ptr) /* 如果 ptr 非空,则完成 */
if(!ptr) /* 如果 ptr 为空,则完成 */
process(think);
process(think());
double pam(int);
double (*f)(int);
f = pam;
double pam(int);
double (*f)(int);
f = pam;
double x = pam(4);
double y = (*f)(4);
double z = f(4);
double pam(int);
double *f1(int);
double (*f2)(int);
int arr[10];
int *p = &arr[0];
int *a = arr;
int *b = &arr;
int *c[10] = &arr;
int (*d)[10] = &arr;
上述代码中:
f1是个函数,返回值类型是个double* 指针
f2是个指针,指向了一个函数,类型就是double (*)(int);
p是个指针,指向数组arr的0号元素首地址
a是个指针,和p一样(数组名arr就是0号元素首地址)
b是个指针,虽然和a一样的值,但指向的是整个数组内存块的首地址,当b进行+1运算时,会增加整个数组内存字节数大小
c是个数组,包含了10个int* 指针元素的数组
d是个指针,指向了一个包含10个int元素的数组
(下一节函数篇也有涉及)
在函数原型中,特征标即参数列表有如下规则:
const double * (*pa[3])(const double *,int) = {f1,f2,f3};
语句书写思路:先写出返回类型和特征标 const double * ()(const double*, int) 再向()内填写一个包含指针的数组表达式*pa[3]
const double * (*(*pa)[3])(const double *, int) = &pa;
语句书写思路:先写出返回类型和特征标const double * ()(const double *, int)再向()内追加 一个 指向包含指针元素的数组的 指针表达式*(*pa)[3]
struct Books *struct_pointer;
struct_pointer = &Book1;
struct_pointer->title;
//previous
struct Books
{
string name;
int book_id;
};
struct Books Book1;
Books newBook;
newBook.name = "abcd";
//now
typedef struct Books
{
string name;
int book_id;
}BOOKS;
BOOKS Book1, Book2;
为了访问结构的成员,我们使用成员访问运算符(.)
struct type_name {
member_type1 member_name1;
member_type2 member_name2;
member_type3 member_name3;
.
.
} object_names;
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
typedef struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
}Books;
文章使用Q&A问题回答形式,内容涉及C++函数部分。
//calling.cpp
#include
void simple(); //原型
int main(){
simple(); //调用
return 0;
}
//定义
void simple(){
cout<<"ok"<
#include
int main(){
char ch;
cin >> ch;
while(ch !='q'){
return -1;
}
return 0;
}
//prototype
void fillArray(int arr[], int size);
void fillArray(int* arr, int size);
void fillArray(int [], int);
void fillArray(int*,int);
//prototype
void show_array(const double [], long);
void show_array(const double arr[], long n);
void show_array(const double *,long);
//prototype
void show_array(int (*arr)[4], int size);
void show_array(int arr[][4], int size);
void show_array(int (*)[4], int);
void show_array(int [][4], int);
char* builder(char c,int n){
char* pstr = new char[n+1];
pstr[n] = '\0';
while(n-->0){
pstr[n] = x;
}
return pstr;
}
void rect_to_polar(const rect* pxy,polar* pda){
cout<<"begain"<distance = (pxy->x + pxy->y);
pda->angle = atan2(pxy->y,pxy->x);
cout<<"OK"<
(上述,指针相关专题也有涉及)
double pam(int);
double (*ptr_f)(int);
#include
double pam(int);
void estimate(int lines, double (*pf)(int));
int main(){
using namespace std;
double (*pff)(int);
pff = pam;
cout<<"pff(4)="<>code;
cout<<"estimate="<
const double * (*pa[3]) (const double * , int);
const double * px = pa[0](av,3);
const double * py = (*pa[2])(av,4);
const double * (*(*pa)[3]) (const double * ,int);
const double * px = (*pa)[0](av,3);
const double * py = *(*pa)[2](av,4);
编译过程最终是生成可执行文件,然后运行,在运行程序时,操作系统将可执行文件,即这些机器语言指令装载到内存中,然后按照指令的地址逐步执行,有时候遇到循环或者条件分支语句时会跳着执行,函数调用也会从一个地址跳到函数所在的首地址,函数结束则返回
当程序跳转到函数地址时,即程序在函数调用后会立即存储该指令的地址,然后把函数参数放到堆栈内存池中,然后直接跳到函标记函数起点的内存块处开始执行函数代码,最后函数结束就跳回去了。
来回的跳跃并记录跳跃位置,需要太多的开销了,因此C++提供了内联函数,无需跳跃,但需要更多内存,即如果程序再10个不同的地方调用同一个内联函数,则这个内敛函数将有10个副本
使用: 函数声明或定义前加关键字inline即可
inline double square(double x) {return x*x;}
int rats;
int & rodents = rats;
double cube (double);
double square(double*);
double atan(double&);
double square(const double &);
#include
struct free_throws{
std::string name;
int made;
int attempts;
float percent;
};
free_throws & accumulate(free_throws&, const free_throws);
int main(){
free_throws ini = {"ini",0,0};
free_throws one = {'A',1,11};
free_throws two = {'B',2,22};
free_throws team = {"all",0,0};
free_throws good = {"good",100,100};
free_throws fact_all;
accumulate(team,one);
fact_all =accumulate(team,two);
cout<
const free_throws & clone2 (free_throws & ft){
free_throws newguy;
newgy = ft;
return newgy; //禁止这么写!
}
const free_throws & accumulate(free_throws&, const free_throws&);
char * left(const char* str, int n =1);
在函数定义部分中则无需上述。
//.cpp
#include
char * left(const char* str, int n = 1):
int main(){
}
char * left(const char* str, int n){
char* p =new char[n+1];
retrun p;
}
术语:多态和重载,指的是同一回事,就是同一物体的多种形式
函数多态=函数重载,即允许函数有多种形式,即有多个同名函数,多态是函数多态,重载其实是函数名称重载
无论多态还是重载,意义就是,名字相同,参数列表不同
函数特征标:函数的参数列表,如果两个函数, 参数数目,参数类型,参数排列顺序,三者都相同,那么他们的特征标相同,显然这里和参数变量的名称无关
C++允许定义函数名称相同的函数, 条件就是其特征标不同
double cube(double x);
double cube(double &x);
//看起来特征标是不同的,其实,类型引用和类型本身是同一特征标
double long(double x);
long long long(double x);
//显然这里的long函数是不允许被重载的,因为特征标是相同的,即返回值类型并不是特征标之一
函数重载很棒,但不要滥用,仅在函数执行基本基本相同任务,但对象的数据类型不同时,可以考虑函数重载
函数模板是通用的函数描述, 使用"泛型"来定义声明函数,这里的"泛型"可以用具体类型替换,比如int, double, long等,具体方法就是通过具体的类型如double作为参数, 传递给模板,编译器就会自动生成该类型函数
另外的名字,①函数模板这种编程方法,也叫作通用编程,因为没有具体化, ②函数具体类型是由参数来表示的,这种模板特性也叫参数化类型
如何创建函数模板??利用关键字template和typename,注意这里typename也是个关键字,区分我们之前的这是我们自己为方便而写的一个概括而已
函数模板的核心: 抛开具体的变量类型, 着重关注数据处理的算法.
意思是变量用T统一代替
template //建立模板,类型是AnyType,如果你想用类class,即把typename关键字改成关键字class即可
//函数定义声明如下
void swapFunction(T &a, T &b)
{
T xjh1; //定义声明变量xjh1是AnyType类型
xjh1 = a; //交换两个变量a和b的值,注意这里都是AnyType类型,可以是int double long long等
a = b;
b = xjh1;
}
int x =1000;
int y =20000;
xjh(x,y)
//xjh()函数获得了两个int类型的值,所以编译器生成了函数的int版本
//案例1
template
void mySwap(S a, S b)
{
S c;
c = a;
a = b;
b = c;
}
//案例2:
template
xjh* findTrueFriend(xjh* friend1, int age)
{
if(age>10)
{
cout<<*friend1<
函数模板也不能总是乱用,就像函数重载,函数重载是在函数执行基本相同任务时,对象类型不同可以用函数重载,而函数模板则是在执行完全相同任务时,如果对象类型不同,则用函数模板
函数重载的基本相同告诉我们,并非要执行完全相同的计算方法即算法,所以函数模板也可以进行重载,即模板重载
void xjh(AnyType &a, AnyType &b) 这里的函数模板,其实你也可以叫模板函数, 的特征标是AnyType &,AnyType &
模板的实例化和具体化
在代码中包含函数模板本身并不会生成函数定义,这个模板的工作只是用于生成函数定义的方案.就是说当编译器通过具体的类型来使用模板时,得到的是模板的实例, 这个模板的实例才是函数的定义声明
联想变量的初始化,那么我们的模板能不能在创建模板时初始化一个类型呢?可以
#include
template
void swap(A&, A&);
//模板重载
template
void swap(A*, A*, int);
int main(){
int i = 10, j = 20;
swap(i,j);
int d1[3] = {0,1,2};
int d2[3] = {3,4,5};
swap(d1,d2,3);
return 0;
}
template
void swap(A& a, A& b){
A temp;
temp = a;
a = b;
b = temp;
}
template
void swap(A*a, A*b, int n){
A temp;
for (int i=0; i
函数的构建模板如下
typeName functionName(parametersList);
void mian(){}
typeName functionName(parametersList)
{
statements
return value;
}
//retrun无法返回数组,但可以是结构,类对象,或者可以把数组作为结构和类对象的成员返回
//函数参数也不能是数组
函数无法返回数组,函数参数也不能是数组,但是可以通过结构、类来使用数组,或者在参数重吧
定义函数时参数列表的变量是形参,调用函数时,是实参,实参一直存在(当然这里是普通变量,new则另说),形参,在函数被调用时,计算机为形参分配内存,函数结束时,释放这些内存块,所以形参叫局部变量,他们的作用域仅在该函数内部
typeName fucntion(int param1, &prama2){}
void main()
{
int a = 10;
int b =11;
function(a, &b);
}
//a和b都是实参,param1和param2都是形参
把数组和函数结合在一起
等等!不是说参数不能是数组,且retrun无法返回数组吗???难道只是做参数传递吗??No
int functionArray(int array[], int n);
//这里的array[]实际上是个指针,还记得指针和数组名其实是一个东西吗?
是的, int array[]表示一个数组, 而方括号是空的,则表示可以传递任何长度的数组给函数functionArray(),其实是错误的, 这里的array是指针!!!但是你把他看成数组又何妨呢?
另外,如果你想通过函数来处理数组, 你最好把数组名,数组类型和数组的长度都告诉函数
如果你想让函数可以修改传进来的数组
void function(double arr[], int n){}
如果不想让函数修改传进来的数组
void function(const double arr[], int n){}
函数有重载,即函数的多态,运算符也有,即运算符重载,运算符的多态.函数在的重载想让你用相同函数名完成基本差不多的操作.
运算符也有重载,实际上你已经学习过了,比如*即是乘号,也是地址运算符,根据运算符左右目来确定具体功能
我们知道函数重载可以自己定义,那运算符其实也可以
如何进行运算符重载?利用operator现有运算符(参数), 这里operator是个函数,不同的是,你需要在函数名后紧跟你需要重载的运算符,比如operator+()
//time.h
class time
{
private:
int hours;
int munutes;
public:
time();
time(int h, int m = 0);
void AddMin(int m);
void AddHr(int h);
void Reset(int h=0, int m=0);
time sum(const time & t) const;
void Show() const;
// 加const表明,该函数只能是只读的,不能修改私有变量的值。加强安全性。
}
//endif 这里sum的参数运用了&运算符重载,即引用
//把stock::sum()函数利用运算符+的重载来实现
//time.h
class time
{
private:
int hours;
int munutes;
public:
time();
time(int h, int m = 0);
void AddMin(int m);
void AddHr(int h);
void Reset(int h=0, int m=0);
time operator+(const time & t) const;
void Show() const;
}
当使用operator+成员函数时, 你也可以通过对象用.运算符来调用该方法函数,你也可以直接用+运算符来操作该
int main()
{
total = coding.operator+(fixing);
total1 = coding + fixing;
}
直接成员运算符.
间接成员运算符->
作用域解析符::
class stock{
private:
char company[30];
int shares;
double share_val;
double total_val;
void set_tot() { total_val = shares*share_val; }
public:
stock();
~stock();
void acquire(const char*, int, double);
void buy(int, double);
void sell(int,double);
void update(double);
void show();
}
//stock10.h
#ifndef STOCK10_H_
#define STOCK!0_H_
#include
class Stock{
private:
public:
Stock(const std::string & co, long n = 0, double pr = 0.0);//构造函数
//注意此处不是默认构造函数,因为co没有提供默认值
Stock();//无参数的默认构造函数
//Stock(const std::string & co = "Error",long n = 0, double pr = 0.1);
//提供全部参数默认值的默认构造函数
~Stock();
};
//stock10.cpp
#include
#include"stock10.h"
Stock::Stock(){
cout<<"无参数默认构造函数"<
//显式调用
//构造函数
Stock food = Stock("word",1,0.0);
//默认构造函数
Stock food = Stock();
//隐式调用
//构造函数
Stock food("word",2,2.01);
//默认构造函数
Stock food;
Stock *p_food = new Stock;
Stock fc_food();
//fc_food()就是一个返回Stock类型的函数
//区分对象的初始化和赋值
Stock stock1 = Stock();
Stock stock2 = Stock("word",1,1.22);
Stock stock3();
Stock stock3;
stock2 = Stock("new",0,0.0);
Stock stock4 = stock1;
stock3 = stock4;
class Stock{
{
private:
public:
void show() const;
}
top = stock1.topval(stock2);
//隐式访问stock1,显式访问stock2
top = stock2.topval(stock1);
//同理
const Stock& Stock::topval(const Stock& s) const{
if(s.total_val > total_val){
return s;
}
else{
return *this;
}
}
Stock array[10];//未显式初始化,则每个对象都会调用默认构造函数
double a = array[0].total_val;
Stock arr0[2] = {
Stock('a');
Stock('b');
Stock('c');
}
//使用构造函数初始化数组成员对象,必须对每一个数组成员都初始化
//mytime1.h
#ifndef MYTIME1_H_
#define MYTIME1_H_
class Time{
private:
int hours;
int minutes;
public:
Time();
Time(int h, int m = 0);
~Time();
Time operator+(const Time &t) const;
};
#endif
//mytime1.cpp
#include
#include "mytime1.h"
//运算符重载
Time operator+(const Time &t) const{
Time sum;
sum.minutes = minutes + t.minutes;
return sum;
}
total = coding.operator+(fixing);
total = coding + fixing;
t4 = t1 + t2 + t3;
t4 = t1.operator+(t2 + t3);
t4 = t1.operator+(t2.operator+(t3));
//stonewt.h
#ifndef STONEWT_H_
#define STONEWT_H_
class Stonewt{
private:
double stone;
public:
Stonewt(double lbs);
Stonewt():
~Stonewt();
};
#endif
//stonewt.cpp
#include
#include "stonewt.h"
Stonewt::Swtonwt(double lbs){
stone = lbs;
}
//main.cpp
#include
#include "stonewt.h"
int main(){
Stonewt mycat;
mycat = 19.222;
}
//stonewt.h
#ifndef STONEWT_H_
#define STONEWT_H_
class Stonewt{
private:
double stone;
public:
explicit Stonewt(double lbs);
Stonewt():
~Stonewt();
};
#endif
//stonewt.cpp
#include
#include "stonewt.h"
Stonewt::Swtonwt(double lbs){
stone = lbs;
}
//main.cpp
#include
#include "stonewt.h"
int main(){
Stonewt mycat;
mycat = 19.222; //ERROR
mycat = Stonewt(19.2); //Right
mycat = (Stonewt) 192.2; //Right
}
//stonewt1.h
#ifndef STONEWT1_H_
#define STONEWT1_H_
class Stonewt{
private:
enum {Lbs_per_stn = 14};
int stone;
double pds_left;
double pounds;
public:
Stonewt(double lbs);
Stonewt(int stn, double lbs);
Stonewt();
~Stonewt();
//转换函数
operator int() const;
operator double() const;
};
#endif
//stonewt1.cpp
Stonewt::operator int() cosnt{
return int (pounds + 0.5); //将成员pounds转换成int类型
}
Stonewt::operator double() const{
return pounds; //将
}
//main.cpp
#include
#include "stonewt1.h"
int main()
//假设StringBad一个类
StringBad motto = StringBad("test",1.2);
//下面四种声明都将调用复制构造函数StringBad(const StringBad&);
StringBad ditto(motto);
StringBad metoo = motto;
StringBad also = StringBad(motto);
StringBad * pStringBad = new StringBad(motto);
int main(){
Stock food = Stock("word",1,0.0); //显式,构函
Stock food = Stock(); //显式,默认构函
Stock food("word",2,2.01); //隐式,构函
Stock food; //隐式,默认构函
Stock *p_food = new Stock; //隐式,默认构函
Stock fc_food(); //函数
Stock milk(food); //复制构函
Stock water = milk; //复制构函
Stock air = Stock(fodd); //复制构函
Stock * pStock = new Stock(food); //复制构函
return 0;
}
//test1.h 注意这里是头文件
class stock
{
private:
std::string company;
long shares;
double share_val;
double total_val;
void set_tot(){cout<<"fuck"<访问符, 来访问公有成员函数,即public下的成员统称#为公有成员,同理private下的成员统称为私有成员
是的,public成员可以通过.操作来进行访问,但是私有成员无法直接通过对象用.操作符来访问,必须由对象通过公有成员来访问私有成员
这种私有成员,很好的杜绝了数据外泄,可以很好的组织程序直接访问数据,这被称为数据隐藏
*/
通常C++程序员,把接口(类定义)放在头文件中,如上程序,而类方法的定义则在包含上述头文件的源代码文件中,那类定义和类方法定义是如何具体关联在一起的呢??
类的思想,把公有接口和实现细节分开,把实现细节放在一起并将他们和抽象分开,即术语封装.
上述的数据隐藏也是一种封装,把实现的细节隐藏在私有部分中也是封装.
类的公有接口表示了设计的抽象组件,如上述的public中的很多个void函数就是对数据操作的方法函数,就是从具体的数据中抽象出来的一种数据操作方法,这些个方法函数声明被放在了.h头文件中, 而这种方法的具体算法,即函数体的结构实现却是在含有.h头文件的源代码文件中实现的.这也是一种封装,即把实现细节和抽象分开
类和结构有什么差别吗?
是的,就目前来看,好像没有什么区别,只不过class是具有public和private的struct,
class和struct最大的、唯一的区别在于, class默认访问的是private而struct默认访问的是public.是的, 结构在C++中进行了扩展,它也可以有class的一些特性了比如public和private
C++通常把类来描述数据和相关操作,而结构仅用来纯粹的表示数据对象
类的成员函数/方法函数/方法,和普通的函数一样有函数声明,有返回值,有参数,有函数体,
区别:
1.函数声明被放在了头文件,而函数具体定义被放在含有头文件的源代码中
2.定义函数成员时,需要用作用域解析符 :: 来标出函数所属的类
当然,类方法可以访问类的private成员(这是我们上边说好的)
类的实现一般包含了3个文件:头文件创建类,定义文件实现类函数,主程序文件调用引用类。
//实现类的成员函数,就是在包含了头文件的源代码中去定义函数体
double stock::acquire()
{
"在类方法函数的定义中,你可以随便调用任何一个成员变量"
}
float stock::buy()
{
...
}
double stock::spell(int a, int b, double sum)
{
...
}
long stock::output(std::string str, name, int age)
{
...
}
OOP的主要目标之一就是对于数据的隐藏,所以组成类接口的成员函数在公有部分,而数据项往往在private部分
为什么成员函数不能放在private部分?因为如果函数在private上就无法通过类对象直接调用成员函数了
如果我非要在类声明中定义一个函数,即类方法的函数体在类声明中了,而且在private中,会怎样? 只要是在类声明中定义的函数,都是内联函数即,自动转成inline函数,在类声明中的函数有个特点:短小精悍
//类声明,即头文件中
//举例说明类声明
//test1.h 注意这里是头文件
class stock
{
private:
std::string company;
long shares;
double share_val;
double total_val;
void set_tot(){cout<<"fuck"<
扩展思路,我也可以在类声明下边定义一个内联函数,只需要指定对应的类就行了,如下
//举例说明类声明
//test1.h 注意这里是头文件
class stock
{
...
};
inline void stock::set_tot()
{
cout<<"fuck"<
由于类的数据项是隐藏的,所以无法像int类型或者结构那样可以定义时初始化,毕竟数据访问是私有的,不能够直接访问
C++对这种特殊的成员函数提供了 名称和适用语法, 我们需要提供方法定义
这类函数的名称和类的名称相同,即stock类中有个成员函数是stock()
这种特殊的成员函数即构造函数,是没有返回值的
创建这种特殊函数即构造函数,这种函数的参数名不能和类的数据成员名相同,因此,应对类的数据成员加前缀m_
//创建一个构造函数,需要两部分,即声明和定义
//声明
//class.h
stock(const string & co, long n =0, double pr =0.0);
//注意,此处的co并没有给出默认值
//定义
//project.cpp
stock::stock(const string & co, long n, double pr)
{
"在类方法中,你可以随便调用任何一个成员变量"
company =co;
if (n < 0)
{
cout<<"fuck"<
构造函数如何使用呢?怎么避免没有使用类而只是在使用构造函数呢??
stock food = stock("world", 250, 12.2);
//第一个stock是类,创建了对象food,第二个stock是类构造函数
stcok food("world",250,122.2);
//这里的stock是类,创建了对象food
stock *food_ptr = new stock("world",250,122.2);
//第一个stock是类,创建了对象指针,第二个stock是类构造函数
注意! 普通的类可以通过对象用.运算符来访问类函数,但是无法访问类构造函数,即构造函数用来创建对象,但不同通过对象来调用
构造函数用来初始化对象,而析构函数用来清理对象,构造函数可通过new来分配内存,,析构函数则用delete完成内存释放,如果没有用new那么析构函数则没有什么任务可做了.
析构函数很特殊,即在类的名字前加~就是它的名字了,如stock类的析构函数是~stock()
和构造函数一样,析构函数没有返回值和声明类型语句,不同的是析构函数也没有参数
析构函数的声明——如何使用析构函数
析构函数若不担任任何重要工作,可以将它写成不执行任何操作的函数
stock::~stock()
{}
析构函数的调用——什么时候用析构函数
编译器决定,通常不会显式调用析构函数
同理,如果我们没有提供析构函数,编译器将自动声明一个析构函数(注意是声明),然后发现有删除对象的代码后,则自动定义一个默认的析构函数(这里才是定义)
构造函数和析构函数都是把声明放在头文件中的,而具体的函数定义是在源代码中的
//stock.h头文件
class stock
{
private:
std::string company;
long share;
double share_val;
void set_tot(){cout<<"fuck"<
//stock.cpp源代码
#include
#include "stock.h"
stock::stock()
{
std::cout<<"这里是默认构造函数"<
可以利用大括号{}进行初始化吗??
stock test1 = {"test1", 100, 2.2};
stock test2 {"test2",200.3.4};
stock test{}
前两个直接调用stock::stock(const std::stirng &co, long n=0, double pr=0.0)
最后一个直接调用stock::stock()
this指针可以帮到你
什么意思呢?就是你的private的数据是只能通过public成员函数来访问的,但无法修改,如何去修改这个数据呢?
C++提供了比修改代码更好的方法来扩展和修改类,那就是类的继承
可以从已有的类派生出新的类,派生类继承了原有的特征,包括全部方法函数
原有的类叫基类, 派生的类叫派生类,或者说继承的类叫做派生类
利用符号 : 来指出RP类的基类是TTP类,加上public表示这是个公有基类,RP作为派生类又叫做公有派生类
派生类的对象会包含基类对象
公有派生类会拥有基类的全部公有成员和私有部分,但基类的私有部分,派生类并不是可以随意访问
派生类继承了基类的实现, 即基类的数据成员
派生类继承了基类的方法,即基类的接口
class TTP
{
...
}
class RP: public TTP
{
...
}
知道了基类和派生类的关系,那我们在派生类的定义中到底需要加什么东西呢???
属于派生类自己的构造函数
追加非基类即额外的数据成员和成员函数
派生类构造函数
派生类不能直接访问基类的私有成员,必须通过基类的方法(函数)进行访问。
Q:我们的构造函数是用来对类成员进行初始化的, 无法访问这些数据成员,我们就无法通过派生类的构造函数来进行数据的初始化了,
A:需要你的派生类构造函数必须使用基类的构造函数
基类对象应该在程序进入派生类构造函数之前创建, 要创建派生类对象,你就必须要先创建基类对象
注意这里并不是说要创建两个对象, 而是C++利用了列表初始化语法来完成了这个操作,怎么完成的?看下边
RatedPlayer::RatedPlayer(unsigned int r , const string & fn, const strng & ln, bool ht):TableTennisPlayer(fn, ln, ht)
{
//派生类 :: 派生类构造函数(参数) : 基类构造函数(基类参数)
...
}
//基类成员初始化列表 :TableTennisPlayer(fn, ln, ht), 这是调用了基类的构造函数
//这里可能晦涩难懂, 举个例子
RatedPlayer xjh(1123, "M", "duck", true);
首先RatedPlayer构造函数, 把xjh对象的参数都给了RatePlayer的构造函数的形参r, fn, in, ht这四个,然后这个形参又作为实参,传递给了TableTennisPlayer的形参fn in ht,于是创建好了一个基类对象,并且你的xjh的参数都存储在了基类对象中,然后, 程序进入了RatedPlayer构造函数的函数体, 执行函数体的语句
创建派生类对象——>派生类构造函数——>对象参数给构造函数的形参——>构造函数形参作为实参给基类构造函数——>基类构造函数创建基类对象——>派生类对象的初始化参数全部存储在该基类对象中——>完成上述创建派生类对象必须创建基类对象的要求
必须首先创建基类对象, 但是像上述代码中, 你不写:TableTennisPlayer(fn, in, ht)这个基类构造函数,程序会自动调用程序
使用派生类
使用派生类,你必须要把基类和派生类的声明放在一起,当然也可以通过#include方法放在不同的.h头文件中, 也可以放在一个头文件中
类的接口定义可以单独放在一个.cpp文件中,只需要你的源代码和这个.cpp文件都包含了上述头文件,就行. 而你的头文件,无需包含源代码和.cpp文件
//tabletenn1.h 头文件
using namespace std;
using namespace string;
class TableTennisPlayer
{
...
}
class RatedPlayer : public TableTennisPlayer
{
...
}
//tableten1.cpp 接口源文件
#include "tabletenn1.h"
TableTennisPlayer::TableTennisPlayer () {}
//这里都是两个类的函数结构定义程序
//main.cpp 主程序源文件
#include "tabletenn1.h"
int main()
{
//直接使用类
TableTennisPlayer xjh1(...);
RatedPlayer xjh2(...);
}
学习了定义和简单的使用类,了解了两个成员函数析构函数和构造函数,这些都是类的通用原理,接下来开始学习类的设计技术
注意! 每个创建的对象,都有自己的内存块,这里可以联想普通变量,类是int类型,而对象就是int类型的数据对象,比如int name=0;
#include
#include "test1.h"
int main(){
stock xjh;//创建类对象
xjh.acquire();
xjh.buy();
xjh.spell();
xjh.output();
// 对于指针
stock * ptr = new stock;
ptr->buy();
ptr->spell();
}
本章对C++编程的思想有明显的提升,特别是文末的代码,应着重学习。
//coordin.h
#ifndef COORDIN_H_
#define COORDIN_H_
struct polar{
double distance;
double angle;
};
void show_polar(polar& );
#endif
用{}包含的语句,视为在同一个代码块中,一个代码中的变量的存储特性是自动的,即随着{}的结束而释放内存。
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由os回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3、全局区(静态区)(static)—全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后有系统释放。
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放。
5、程序代码区—存放函数体的二进制代码。
int global = 100; //静态持续变量,外部链接性,全局作用域
static int one_file = 50; //静态持续变量,内部链接性,全剧作用域
int main(){
}
void function1(int n){
static int count = 0; //静态变量,无链接性,局部作用域
int llama = 0; //自动变量,无链接性,局部作用域
}
extern const int states = 50;
//file1.cpp
#include
double warming = 0.32321;
int main(){
}
//file2.cpp
#include
extern double warming;
int main(){
}
//对于内置标量,用new 类型名(初始值)
int* pi = new int(6);
double* pd = new double(12.1);
//对于结构或数组,用new 结构名/数组名 {初始值}
struct where {double x, double y, double z};
where* one = new where{1.2, 1.2, 1.1};
int* ar = new int[4] {1,2,3,4};
#include
struct chaff{
char dross[20];
int slag;
};
char buffer1[50];
char buffer2[100];
int main(){
chaff *p1,*p2;
int *p3,*p4;
p1 = new chaff;
p2 = new int[20];
p3 = new(buffer1) chaff;
p4 = new(buffer2) int[20];
return 0;
}
namespace Jill {
char* goose(const char*);
}
Jill::goose();
首先给出头文件
//namesp.h
#include
namespace pers{ //设置一块名为pers的声明区域
struct Person{
std::string fname;
std::string lname;
};
void getPerson(Person&);
void showPerson(const Person&);
}
namespace debts{ //设置一块名为debts的声明区域
using namespace pers; //将pers名称空间的名称在当前声明区域内有效
struct Debt{
Person name;
double amount;
};
void getDebt(Debt&);
void showDebt(const Debt);
double sumDebts(const debt ar[], int n);
}
其次给出相关函数定义的源代码
//namesp.cpp
#include
#include "namesp.h"
namespace pers{
using std::cout;
using std::cin;
void getPerson(Person &rp){
cout<<"Enter first name:";
cin>>rp.fname;
cout<<"Enter last name:";
cin>>rp.lname;
}
void showPerson(const Person& rp){
std::cout<>rd.amount;
}
void showDebt(const Debt& rd){
showPerson(rd.name);
std::cout<<":$"<
最后给出调用的main主程序
#include
#include "namesp.h"
int main(){
using debts::Debt; //将头文件namesp.h中的第2个名称空间debts的Debt在当前声明区域有效
using debts::showDebt;
Debt golf = {
{"Benny","Goatsniff"},
120.0
};
showDebt(golf);
return 0;
}
多态是面向对象的三大特征之一,其它两大特征分别
是 封装 和 继承
所谓 多态,简单来说,就是当发出一条命令时,不同的对象
接收到同样的命令后所做出的动作是不同的
静态多态和动态多态
静态多态
静态多态,也叫 早绑定
看如下实例:
class Rect
{
public:
int calcArea(int width);
int calcite(int width, int height);
};
定义一个矩形类:Rect,其中有两个同名成员函数:calcArea(),显然
二者互为重载(名字相同,参数可辨)
当实例化一个 Rect 的对象后,就可以通过对象分别调用这两个函数,
计算机在编译时,就会自动调用对应的函数
int main()
{
Rect rect;
rect.calcArea(10);
rect.calcArea(10,20);
return 0;
}
即程序运行之前,在编译阶段就已经确定下来到底要使用哪个函数,
可见:很早就已经将函数编译进去了,称这种情况为早绑定或静态多态
动态多态
动态多态,也叫晚绑定
看如下实例:
当前要计算面积,于是分别给圆形和矩形下达计算面积的指令,
作为圆形来说,它有自己计算面积的方法,作为矩形来说,它
也有自己计算面积的方法
显然,两种计算面积的方法肯定不同,即对不同对象下达相同
指令,却做着不同的操作,称之为晚绑定或动态多态
动态多态是有前提的,它必须以封装和继承为基础。
在封装中,将所有数据封装到类中,在继承中,又将封装着的各个类使其形成
继承关系
只有以封装和继承为基础,才能谈到动态多态,动态多态
最起码有两个类,一个子类,一个父类,只有使用三个类
时,动态多态才表现的更为明显
看如下实例:
定义一个形状类:Shape,它有一个计算面积的成员函数:calcArea()
再定义一个圆类:Circle,它公有继承了形状类 Shape,并有自己的构造函数和计算面积的函数
再定义一个矩形类:Rect,它公有继承了形状类 Shape,并有自己的构造函数和计算面积的函数
class Shape
{
public:
double calcArea()
{
cout<<"calcArea"<
在使用时:
int main()
{
Shape* s1 = new Circle(4.0);
Shape* s2 = new Rect(3.0,5.0);
s1->calcArea();
s2->calcArea();
return 0;
}
>>calcArea
>>calcArea
可以使用父类指针指向子类对象,但结果却不尽如人意,因为调用到
的都是父类的计算面积的函数,即会打印出两行calcArea
如果想要实现动态多态,就必须使用 虚函数
用virtual 修饰成员函数,使其成为 虚函数
如下:在父类中,把想要实现多态的成员函数前加上virtual 关键字,使其成为虚函数,
在定义子类Circle 时,给计算面积的同名函数也加上virtual 关键字,不加也可以,会自动添加
class Shape
{
public:
virtual double calcArea()
{
cout<<"calcArea"<
使用父类指针指向子类对象,调用函数时,调用的就是对应子类的计算面积函数
即: 调用谁的函数,看谁初始化的
Shape* s1 = new Cricle(4.0);
//后续调用virtual时,将调用Circle类的
如下:
int main()
{
Shape* s1 = new Circle(4.0);
Shape* s2 = new Rect(3.0,5.0);
s1->calcArea();
s2->calcArea();
return 0;
}
>> 50.24
>> 15.0
final
final修饰虚函数
C++ 中增加了 final 关键字来限制某个类不能被继承,或者某个虚函数不能被重写
如果使用 final 修饰函数,只能修饰虚函数,并且要把final关键字放到类或者函数的后面,这样就能阻止子类重写父类的这个函数了
class Base
{
public:
virtual void test()
{
cout << "Base class...";
}
};
class Child : public Base
{
public:
void test() final
{
cout << "Child class...";
}
};
class GrandChild : public Child
{
public:
// 语法错误, 不允许重写
void test()
{
cout << "GrandChild class...";
}
};
final修饰类
使用 final 关键字修饰过的类是不允许被继承的,也就是说这个类不能有派生类。
应在类的名字后添加final关键字
class Base
{
public:
virtual void test()
{
cout << "Base class...";
}
};
class Child final: public Base
{
public:
void test()
{
cout << "Child class...";
}
};
// error, 语法错误
class GrandChild : public Child
{
public:
};
override
overide修饰虚函数
override 关键字确保在派生类中声明的重写函数与基类的虚函数有相同的签名,同时也明确表明将会重写基类的虚函数,这样就可以保证重写的虚函数的正确性,也提高了代码的可读性,和 final 一样这个关键字要写到方法的后面。使用方法如下:
class Base
{
public:
virtual void test()
{
cout << "Base class...";
}
};
class Child : public Base
{
public:
void test() override
{
cout << "Child class...";
}
};
class GrandChild : public Child
{
public:
void test() override
{
cout << "Child class...";
}
};
函数签名 :
C++中的函数签名(function signature):包含了一个函数的信息,包括函数名、参数类型、参数个数、顺序以及它所在的类和命名空间。
普通函数签名并不包含函数返回值部分,如果两个函数仅仅只有函数返回值不同,那么系统是无法区分这两个函数的,此时编译器会提示语法错误。
函数签名用于识别不同的函数,函数的名字只是函数签名的一部分。在编译器及链接器处理符号时,使用某种名称修饰的方法,使得每个函数签名对应一个修饰后名称(decorated name)。编译器在将C++源代码编译成目标文件时,会将函数和变量的名字进行修饰,形成符号名,也就是说,C++的源代码编译后的目标文件中所使用的符号名是相应的函数和变量的修饰后名称。
C++编译器和链接器都使用符号来识别和处理函数和变量,所以对于不同函数签名的函数,即使函数名相同,编译器和链接器都认为它们是不同的函数。
不同的编译器厂商的名称修饰方法可能不同,所以不同的编译器对于同一个函数签名可能对应不同的修饰后名称。
lambda 表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。lambda 表达式的语法形式简单归纳如下:
[capture](params) opt -> ret {body;};
其中 capture 是捕获列表,params 是参数列表,opt 是函数选项,ret 是返回值类型,body 是函数体。
分别阐述上述名称:
capture 捕获列表 []: 捕获一定范围内的变量,
默认状态下 lambda 表达式无法修改通过按值方式捕获外部变量,如果希望修改这些外部变量,需要通过引用的方式进行捕获。
[] - 不捕捉任何变量
[&] - 捕获外部作用域中所有变量,并作为引用在函数体内使用 (按引用捕获)
[=] - 捕获外部作用域中所有变量,并作为副本在函数体内使用 (按值捕获), 拷贝的副本在匿名函数体内部是只读的
[=, &foo] - 按值捕获外部作用域中所有变量,并按照引用捕获外部变量 foo
[bar] - 按值捕获 bar 变量,同时不捕获其他变量
[&bar] - 按引用捕获 bar 变量,同时不捕获其他变量
[this] - 捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限, 如果已经使用了 & 或者 =, 默认添加
params参数列表 (): 和普通函数的参数列表一样,如果没有参数参数列表可以省略不写。
opt 选项, 不需要可以省略
auto f = [](){return 1;} // 没有参数, 参数列表为空
auto f = []{return 1;} // 没有参数, 参数列表省略不写
int a = 0;
auto f1 = [=] {return a++; }; // error, 按值捕获外部变量, a是只读的
auto f2 = [=]()mutable {return a++; }; // ok
ret返回值类型:在 C++11 中,lambda 表达式的返回值是通过返回值后置语法来定义的。
body函数体:函数的实现,这部分不能省略,但函数体可以为空。
补充:
1⃣️C++11 中允许省略 lambda 表达式的返回值,
一般情况下,不指定 lambda 表达式的返回值,编译器会根据 return 语句自动推导返回值的类型,但需要注意的是 labmda表达式不能通过列表初始化自动推导出返回值类型。
// ok,可以自动推导出返回值类型
auto f = [](int i)
{
return i;
}
// error,不能推导出返回值类型
auto f1 = []()
{
return {1, 2}; // 基于列表初始化推导返回值,错误
}
2⃣️为什么通过值拷贝的方式捕获的外部变量是只读的:
lambda表达式的类型在C++11中会被看做是一个带operator()的类,即仿函数。
按照C++标准,lambda表达式的operator()默认是const的,一个const成员函数是无法修改成员变量值的。
mutable 选项的作用就在于取消 operator () 的 const 属性。
3⃣️对于没有捕获任何变量的 lambda 表达式,还可以转换成一个普通的函数指针
using func_ptr = int(*)(int);
// 没有捕获任何外部变量的匿名函数
func_ptr f = [](int a)
{
return a;
};
// 函数调用
f(1314);