Cpp notes
仅仅是流水账式的记录。
###先来一个demo
#include <iostream>
int main(){
std::cout << "Hi Gril" << std::endl ;
std::cout << "ret is " << (n1 + n2) << "apple" << std::endl;
std::cin.get();
return 0;
}
- std表示在该命名空间下
- cout是console out的缩写,表示一个流,一个用来传输数据的通道,后面的
<<
符号,表示把后面的内容插入到流中。 - std::endl 表示把它前面的内容先输出,然后将光标移动到下一行,可以在字符串尾部加入
\n
来达到同样的效果 - cin.get(),cin表示console in,等待键盘输入
###编译cpp
//mac下使用内置的make编译(记住不是make mian.cpp)
make main
//或者
g++ -o outfile main.cpp
###变量
数值数据类型有:
类型 | 字节长度 | 取值范围 |
---|---|---|
short | 2 | 小整数, 无符号:-32768~32767,有符号:[0, 2^16) |
int | 2,4 | 整数,取值与short和long相同 |
long | 4 | 大整数,有符号:[0, 2^32), … |
整数分为有符号和无符号,浮点数还有float和double float,它没有无符号的说法。用到的时候查手册。
注意:这些类型的内存需求量可能因os和计算机平台的不同而不同。
int是标准的数据类型,所以下面两行等价:
unsigned int number;
unsigned number;
###格式化数值
//make sure输出的流中的数值最多只能包含四个数字(四舍五入)
std::cout.precision(4)
//上面的方法是限制数值所有部分的位数,如果我们只要限制小数部分的位数可以如下设置
std::cout.setf(std::ios::fiexed);
std::cout.setf(std::ios::showpoint);
std::cout.precision(3);
std::cout << 2.3459 ; //2.346
第一句表示使用定点记号,第二句表示不能省略小数点。另外,在为数值设置输出精度的时候,还能设置输出宽度:
std::cout.fill('*');
setw(10);
std::cout << '-' << 1.1 << '-' ;
###类型转换
隐式转换,把某一个类型的数值赋值给另一个类型的时候(不推荐):
int a = 0;
float b = 8.2 ;
a = b ; //a is 8
b = a ; //b is 8.0000
另一种是显示转换,也叫强制转换:
float price = 1.23 ;
std::cout << int (price) ;
//此时输出的是1,但是price的值还是原来的
注意:从实数到整数的转换不做舍入处理。比如:3.8会变为3。如果要四舍五入,可以手动加个0.5,之前在OC里面做数据转换就是这么干的。
更为复杂的类型转换(指针和对象):
static_cast, const_cast,reinterpret_cast, dynamic_cast
//format
operator <type> (var)
int km = static_cast<int>(x+0.5);
单字符串:
char str ;
str = 'A';
char本质是一个小整数,只占用一个字节,取值为:[0,2^8)。cpp使用char类型以数值格式保存字符,这些字符通常来自ascii字符集,但在某些机器上或是国际化环境里可能有变化。在ascii字符集里面,下面两个语句是等价的:
char word = "J";
char word = 74 ;
ascii还包含特殊字符,比如换行符等。可以只用类型转换来查看对应的整数值。
char tab = '\t' ;
std::cout << int (tab) ;
除法
CPP中,整数相除结果还是整数,所以要正确计算除法,一定要用实数,只要参与计算的两个数中一个是浮点数,结果也会是浮点数
float a=3,b=2.0;
float c ;
c = a/b; //1.5
注意:
- 另外
%
只能用于整数。 - 实数类型存储的知识一个近似值(2.0存储为1.9999),所以浮点数的结果不精确,不要去比较浮点数是否相等
注意:因为char本质是一个整数,所以,可以直接对它进行算术运算:
char temp = 'A'
temp = temp +1 ; //temp is B
###字符串声明和常量
std::string str1, str2;
str.size()
//常量
const int PI = 3.14 ;
另外,cpp里面还可以使用一个预处理指定来定义(编译器不对预处理做验证)
#define PI 3.14
###操作符和控制结构
####if条件
支持三元选择符 cond ? a : b
;
支持字符串等价判断
####支持swtich case 判断体。
- 可以多个case对应一个代码块
- break语句非常重要,只要遇到break就退出switch循环
- 如果漏掉了break(或者故意没写),程序将继续执行它后面的语句,不管他们是否属于另一个分支。
- 只能使用整数和char来比较。
支持while和doWhile
数据的溢出处理,比如unsigned short 的65535 + 1 -> 0 ; 0 -1 -> 65535 。具体的和OS有关。 如果是涉及实数处理,结果往往不是翻转,而是变成inf或者infinity,或者NaN
####for循环
变量作用域:for里面初始化的变量只在for里面有效
###输出和输入
前面我们使用cout « 输出了字符流,那么我们也可以使用cin来输入内容
//char用来存储单个字符哈
char str;
//将输入的内容赋值给str
std::cin >> str ;
cin对象在读入一个字符时将忽略空格,所以敲入空格Y回车
和只敲入Y回车
是一样的效果。如果你想读入任意一个字符,包括各种空白字符,请使用cin的get函数。
//todo 是说使用get可以获取输入的全部字符吗?
char myChar;
std::cin.get(myChar);
####丢弃输入数据
如果在一个代码块中先后使用两次cin,如:cin » xx , cin.get() ; 你会发现在第一次输入字符后,持续会一直执行到结束,而不会出现期待中的——在第二次cin.get()时暂停下来。
cin对象从控制台读入数据时,当用户按下回车时,用户已经输入的数据会一次性发送到Cpp,当cpp收到数据后,就把输入数值赋值,比如你按下的是 Y return
,Cpp会把Y赋值给变量,但return还留在输入流里,这样,当执行到cin.get()时,Cpp会立刻读入那个回车而不是等待你再次按下回车(技术上来说,数据会进入一个由程序为之保留的缓冲区)
这个机制在需要需要多次数据的时候会遇到问题。解决方案之一是及时清除多余的缓冲区的数据,可用ignore()。
//len表示要丢失字符的个数
std::cin.ignore(len);
//删除整个缓冲区
std::cin.ignore( std::cin.gcount()+1 )
另一种方案:提供一个表示停止字符的参数,告诉ignore读到什么字符停止执行:
//丢失10个字符 或 遇到第一个换行符
std::cin.ignore(10, '\n');
####输入字符串
定义一个字符串,然后将输入值保存到该变量
//todo 这里申明变量为何要加std namespace?
std::string input ;
std::cout << 'enter str ';
std::cin >> input ;
这将把用户输入的字符串赋值给input,直到第一个空白字符(空格,制表符,换行,回车都将结束一个输入字符串)
####一次读取多个值
由于cin使用空白字符作为输入str的结束标志,所以Jim Green
当被当作两个输入值,我们可以将两个值赋值给两个变量:
std::string firstName, lastName ;
std::cout << 'enter your firstName and lastName' ;
std::cin >> firstName >> lastName ;
注意:两个值的类型可以不同
###读取整行输入
前面的输入都是以空白字符结束,那么问题来了,我要读取整行文字怎么办?我们可以使用getLine函数,使用时,需要将输入了的名字cin
当作第一个参数,用来保存的变量当作第二个参数。
std::string sentence;
std::cout << 'enter a sentence ' ;
std::getLine(std::cin, sentence) ;
getLIne读取输入直到它遇到第一个换行符。换行符本身不会被记录到变量中,地上那个参数定义停止字符,我们让他遇到#
号来停止。
####对输入数据进行检查
cin在检查数据的时候有两个地方容易出问题:一是CIN在调用失败(无法把一个值赋值给变量时)程序仍会像cin调用成功时执行;另外是只有cin接收到了某些数据,程序才会执行,如果用户只是猛按回车而没有输入数值,程序将会一直等在那里。
cin有方法可以检测工作情况,返回布尔值:
- eof(), 如果到达文件结尾
- fail(), 如果无法工作
- bad(), 比较严重的原因(比如内存不足)
- good(), 如果没有情况
遇到问题时,可以调用clear()清除出错状态,调用ignore()丢失缓冲区数据。
####数据输出到文件
想要对文件进行读写,需要用到stream库。
#include <stream>
创建一个ofstream(output file srtream)类型的变量
std::ofstream fileOutput('fileName');
这行代码把fileOutput变量和一个指定的文件关联在一起。filename可以是相对路径,也可以是绝对路径。使用is_open来检查文件是否被打开。这里比较奇怪的一点是:fileOutput貌似是一个带变量的方法
fileOutput.is_open()
fileOutput.good()
// 写文件:
fileOutput << "write data \n";
fileOutput.close();
注意:fileOutput的默认行为是如没有文件,则创建,如果有,则覆盖。如果要追加数据,需要使用ios::app标志
std::ofstream fileOutput('test.txt' , std::ios::app)
要点:
- 引入fstream将自动引入iostream
- 可以将创建一个ofstream变量,然后调用open方法;`std::ofstream fileOutput; fileOutput.open(‘f’)
- ios::nocraete 和 ios::noreplace ,如果同时使用多个标志,就要用
|
隔开,如fileOutput('x.txt', (std::ios::nocreate) | (std::ios::noreplace));
一根斜线和两根反斜线的区别(slash , backslash)
- 非win系统使用
/
分割文件路径的子目录,比如./
代表当前目录 - win系统使用反斜线来分割路径,蛋因为反斜线在Cpp里面有特殊的含义,用来引导转义序列,如
\n
,所以必须使用两个\\
来才能正确的引用目录。如C:\\windows\\
。
####使用文件输入
读取文件需要fstream库。
#include
默认情况,getLine会读取字符,直到遇到换行。最后别忘了关闭文件。
###5函数
####定义个人函数
首先定义函数原型(放在最前面,方便编译器检查)
//可选参数必须放在最后
void say( int A , int B = 100);
然后定义函数体:
void say (int A , int B) {
cout << 'hello';
}
使用inline
关键字在main之前创建内联函数。
inline void FN(){ }
####函数重载
用同样的名字再定义一个有不同参数(个数,类型),但有着同样用途的函数。
使用场景:需要对于不同类型参数做同样处理的场合。
void say(int A);
void say(char A);
####作用域
- 在代码块里面申明的只能在代码块里面使用(比如if或者循环语句)
- 函数里面申明的变量
函数内部的静态变量和非静态变量
的区别:他们的值在程序的生命周期内及时多次被调用也不会自动重置
void fn() {
static int count =1;
count++;
}
比如上面的代码,第一次调用时count是1,结束时是2;第二次调用是count的值是2,结束时是3。如果没有用static关键字,每次调用fn函数值,count的值都是1. (JS中都是这种方式)
###6复杂的数据类型 ####数组
不同把不同类型的数据放同一个数组
float arrNumber(10);
int nums[] = {1,2,3};
####指针
查看指针地址: &var
指针就是用来存放内存地址的特殊变量:type *pointName
其类型必须和保存地址的指一致。
通过指针访问数据:*var
对于数字,变量的内存地址是指向base地址的(第一个元素的地址),所用*point
返回的是数字第一个元素的值,如果要访问其他元素,就需要对指针做运算了。
####结构
struct sName {
type varName;
}
当处理一个包含多种属性的数据,结构是一个不错的选择。
struct employee {
shotr sex;
int age;
}
使用
employee el ;
el.age ;
el.sex
//init
employee el = {18, 'male'}
####指向结构的指针
struct person {
int age;
char gender ;
}
person Jim = {20, 'M'};
//创建一个指向该结构的指针, JimGreen的值为Jim的内存地址
person *JimGreen = &Jim ;
//通过指针来访问结构的变量
(*JimGreen).gender ;
JimGreen->gender ;
cpp中可以在定义结构后面立即创建该类型的变量:
struct person {
int age;
} Jim, Lilei ;
还可以创建一个以结构为元素的数组:
person group[10] ;
另一种类型:联合(union)。和结构相似,但他每次只能存储这些之中的一个。如:某个联合里面有三个变量ABC,我先给A赋值,如果再给B赋值,那么A的值将丢失。
####传地址给函数
void changeVar (int *myVar){
//对指针解引用
*myVar = 123;
}
int num = 1 ;
changeVar(&num);
###以引用传递的方式传递参数
解决上述方案臃肿的语法:
void changeVar(int &myVar){
myVar = 123;
}
在定义变量时加&,myVar不是指针,而是那个将被传递变量的一个别名。在函数里面的操作将会反应在原始变量上。如,下面两个变量指向同一个内存:
int n1 = 11;
int &n2 = n1 ;
两个概念:typedef 和 enum
typedef int * intPointer ;
intPointer myVal;
###7 对象
####类 在类的申明里面,要加上public,private和protected三个词之一。标识属性的可访问性
在类中无法给常量赋值,但可以通过创建静态常量来绕开:
class Car {
public:
static const float Tank = 12 ;
}
####定义构造器
- 构造函数的名字和它所属类的名字相同
- 实例化后立即自动调用这个类的构造函数
- 它不返回任何值
创建构造器
class Person{
char gender;
Person(char g);
}
//定义构造函数
Person::Person(char g){
gender = g;
}
一个类可以有多个构造器,名字相同,只是输入参数和类型有差异,创建对象时,程序会根据参数的个数和类型去挑选一个正确的构造器。
析构函数
在销毁一个对象时,相同会调用一个特殊的方法——析构函数。析构和构造是相反的关系。和构造函数同名,前面多一个~
class Person {
char gender;
Person(char g);
~Person();
}
####this
cpp中的对象是一种特殊的结构,包含变量和函数等,运行时,这些都放在内存中,而this就指向对象本身的内存地址,所以可以通过this来访问对象内部的属性。
###8 继承
创建子类时,基类的构造器先被调用,然后调用子类自身的构造函数。 继承下的析构什么时候发生
####重载方法 cpp允许两个同名的方法共存(参数不同),在调用时,会根据参数来执行对应的方法 所以,对方法进行覆盖的时候一定要注意保持参数相同(类型,个数),不然就会当作重载了。
友元关系。是类/函数之间的一种关系。允许友元访问对方的私有属性。添加友元:
class X : public baseObj {
public:
friend class Y
}
//这样Y就要访问X的私有属性了
###9 OOP
####静态属性和方法
静态属性在所有实例中共享, 使用时一定要手动分配内存。
static int count;
//allocate memory
int Pet::count = 0 ;
静态方法尽量通过类来访问: classX::methodName()
####虚函数
直接创建一个指针
int* agePt = new int ;
//通过解引用赋值
*agePt = 18;
delete agePt ;
简单理解:帮助子类覆盖基类的方法
编译时(compile-time),cat和dog都是pet类型的指针,而pet拥有play方法, 所以cat的dog的play都对应pet的play方法 运行时(runtime),cat和dog都是cat和dog类型的指针,于编译时不同,所以我们需要告诉编译器,让子类在运行时根据其 类型调用正确的方法。所以我们就需要在基类中申明为虚方法,见9.2.2
虚构函数都是虚函数(既然这样的化,何必要 virtual ~Game();
直接~Game()
就好了)
####抽象方法
多次继承,在基类中不实现该方法,子类调用时去实现
virtual void play()=0;
有一个抽象方法,还必须有一个普通的虚函数,否则会报错
####重载操作符
newType operator+ (newType rhs);
####多重继承
人, 老师 | 学生,主角 |
class zhujiao : public teacher , public student {}
####虚继承
class Teacher : virtual publis Person {}
保证teacher的子类只能拥有一份Person属性。 在使用多重继承的时候,一定要注意继承了基类的多少个副本。最好的实践:基类中只有抽象函数,没有属性。这称之为接口。
###临时记录I
双冒号
表示成员归属 A::member
表示全局作用域
char a; //全局变量
int abc()
{
char a;
a = 'a'; //局部变量
::a = 'b';//全局变量
}
定义成员方法
class CA {
public:
int ca_var;
int add(int a, int b);
int add(int a);
};
int CA::add(int a, int b)
{
return a + b;
}
###VS配置
安装VA, VSVIM VSVIM的配置命令(为了防止误载入,可以在设置里面只载入vsvimrc格式文件):
:set vimrcpaths?
:set vimrc?