一.变量和基本类型
1.字符和字符串区别
单引号括起来的一个字符称为char型,如‘abs’
双引号括起来的零个或多个字符构成字符串,如“hello world”
编译器会在每个字符串的结尾处添加一个空字符‘\0’,所以字符串的实际长度比它的内容多1
2.类型转换
当一个算数表达式中既有无符号数又有int值时,那个int值就会转换成无符号数。
例:
unsigned u = 10; int i = -42; std::cout << i + i<<std:endl;//输出-84 std::cout << u + i<<std::endl;//输出4294967264,如果int占32位 -1=4294967296-1
2.1 四个显式强制转换
static_cast:只要不包含底层const,都可使用
dynamic_cast:支持运行时的类型识别
const_cast:只能改变运算对象的底层const
reinterpret_cast:为运算对象的位模式提供较低层次上的重新解释
3.初始化和赋值的区别
初始化和赋值是两个完全不同的操作,初始化是创建变量时赋予其一个初始值。
赋值:把对象的当前值擦除,以一个新值来替代
4.指针数组和数组指针
指针数组:数组元素全为指针char *pl[5]={"你","好",'"世","界"}
数组指针:指向数组首元素的地址的指针,其本质为指针(这个指针存放的是数组首地址的地址,相当于2级指针,这个指针不可移动) char num={"你","好"}
5.指针
int num = 10; int *p = #//p是指向num的指针,或p中存放num的地址 cout <<p<<endl;//打印num的地址 cout<<*p<<endl;//打印num的值
指向指针的指针
int **p2= &p;//p2指向一个int型的指针
6.引用
引用标识符以符号&开头
int i = 10; int &r=i;//r是一个引用,与i绑定在一起 r=20;//则i=20 cout<<&r<<&i;//输出i和r的地址相同 cout<<r<<i<<endl;//输出r和i的值均为20
6.1 对指针的引用
语法:类型 &引用名=指针名;//可以理解为:(类型) &引用名=指针名,即将指针的类型当成类型*
#include<iostream> using namespace std; int main(){ int a=10; int *ptr=&a; int *&new_ptr=ptr; cout<<&ptr<<" "<<&new_ptr<<endl; return 0; }
7.const限定符
const对象创建后值就不能改变
7.1 顶层const
指针本身为常量
int i=0; int *const pi = &i;//顶层const,不能改变pi的值 pi=10;//错,不能改变pi的值 *pi=20;//对,可以改变i的值
7.2 底层const
指针所指的对象为常量
const int r=32; const int *p = &r;//底层const,允许改变p的值 p=12;//对,改变p的值 *p = 20;//错,不能改变r的值
8.constexpr和常量表达式
8.1 常量表达式
值不会改变并且在编译过程就能得到计算结果的表达式。
例:字面值,用常量表达式初始化的const对象
const int i=20;//i是一个常量表达式,字面值为常量表达式 const int j=i+1;//j是一个常量表达式 const int s=get_size();//s不是常量表达式
8.2 constexpr
c++新标准规定,允许将变量声明为constexpr类型对象以便编译器来验证变量的值是否是一个常量表达式。声明为constexpr变量一定是一个常量,而且必须用常量表达式初始化
constexpr int sa=20;//20是常量表达式,初始化正确 constexpr int la=sa+19;//sa+19是常量表达式,初始化正确 constexpr int s=size();//只有当size是一个constexpr函数时,才是一条正确的语句
9.decltype类型指示符
c++新标准中引入decltype,作用是选择并返回操作数的数据类型。
decltype(f()) sum = x;//sum的类型为函数f的返回类型 const int ci=0,&cj = ci; declytpe(ci) x=0;//x的类型是const int decltype(cj) y=x;//y的类型是const int &,y绑定到变量x
二.字符串、向量和数组
1.string标准库
string对象和字面值相加
string对象可以和一个字面值相加
string s1= "hello";
string s3 = s1+"world";
两个字面值不能直接相加:如 string s4= "hello"+"world";---错误
2.范围for语句!!!!!
用于遍历给定序列中的每个元素并对序列中的每个元素执行某种操作
表示格式:
for(declaration : expression) • statement
例:
string str("some string"); for(auto c : str) //遍历str中的每个字符 cout<< c << endl;//输出当前字符
例:将字符串中的所有s字符改为a---------试用于对一个字符串中的字符进行修改
string str("some string"); for(auto &c : str)//c为引用,依次绑定str中的每一个字符(即c为啥str中的那个字符便是啥),如果字符为s则将s改为a { if(c=='s') c='a'; } cout << str <<endl;
结果:
3.处理string对象中的单个字符的方法
(1.使用下标的方式:s[0]为第一个字符,s[1]为第二个字符,类推,直到s[s.size()-1]
(2.使用迭代器
4.vector使用
头文件:#include<vector>
(1)定义和初始化对象--注意区别括号和大括号
vector<int> num;//初始状态为空 vector<vector<int>> fi;//元素为vector的vector对象 vector<string> str(3,"hello");//初始化3个string元素,每个均为字符串hello vector<string> str2{"hello","my","world"}; 等同于vector<string> str2={"hello","my","world"};//初始化vector对象str2包含三个元素,即3个字符串 vector<int> num1(10);//num1中有10个0 vector<int> num2{10};//num2中有1个10
(2)向vector添加元素
使用push_back向尾端添加元素
vector<int> nb; for(int i=0;i<10;i++) nb.push_back(i);
(3)其他操作
vector<string> v;
v.empty;//如果v为空,则为真,否则假 v.size();//v的大小 v[n];//返回第n个元素的引用
例:
vector<int> num{ 1,2,5,4,7 }; • for (auto &i : num)//引用num中的每个元素 • i *= i; • for (auto c : num) • cout << c << endl;
5.迭代器
(1)迭代器和指针的区别:
迭代器:
(1)迭代器不是指针,是类模板,表现的像指针。他只是模拟了指针的一些功能,通过重载了指针的一些操作符,->,*,++ --等封装了指针,是一个“可遍历STL( Standard Template Library)容器内全部或部分元素”的对象, 本质是封装了原生指针,是指针概念的一种提升(lift),提供了比指针更高级的行为,相当于一种智能指针,他可以根据不同类型的数据结构来实现不同的++,--等操作;
(2)迭代器返回的是对象引用而不是对象的值,所以cout只能输出迭代器使用*解引用后的值而不能直接输出其自身。
(3)在设计模式中有一种模式叫迭代器模式,简单来说就是提供一种方法,在不需要暴露某个容器的内部表现形式情况下,使之能依次访问该容器中的各个元素,这种设计思维在STL中得到了广泛的应用,是STL的关键所在,通过迭代器,容器和算法可以有机的粘合在一起,只要对算法给予不同的迭代器,就可以对不同容器进行相同的操作。
指针:
指针能指向函数而迭代器不行,迭代器只能指向容器;指针是迭代器的一种。指针只能用于某些特定的容器;迭代器是指针的抽象和泛化。所以,指针满足迭代器的一切要求。
总之,指针和迭代器是有很大差别的,虽然他们表现的行为相似,但是本质是不一样的!一个是类模板,一个是存放一个家伙的地址的指针变量。
(2)迭代器使用
定义迭代器:vector<int>::iterator it;//能读写vector<int>中的元素
vector<int>::const_iterator it;//只能读元素,不能写
或使用auto方式,自动声明格式
begin()指向第一个元素的迭代器,end()指向最后一个元素的下一个元素
若v.begin()==v.end(),则容器为空
迭代器使用(++)运算符:指向后一个位置
cbegin(),cend()指向容器位置与begin()和end()同,但返回值为const_iterator
auto it2 = v.cbegin();//it2类型为const_iterator
5.1 迭代器运算
iter + n ;//指向位置向后移动n个位置
iter - n ;//指向位置向前移动n个位置
例:使用迭代器完成二分搜索
vector<int> number1 = { 12,45,89,120,230,450 }; int find_number =45; vector<int>::iterator start, end,mid; start = number1.begin(); end = number1.end(); mid = number1.begin()+(end - start) / 2; while (mid != end && *mid != find_number) { if (find_number < *mid) end = mid; else start = mid + 1; mid = start + (end - start) / 2; } if (mid == end) cout << "not find" << endl; else cout << " find" << find_number<<endl;
6.数组
字符数组:
const char a1[5]="hello";//错--没有空间存放空字符
char a2[]="hello";
char a3[]={'h','e','l','l','o','\n'};与上相等
数组声明:
int *ptr1[10];//ptr1是一个含有10个整型指针的数组,即指针数组 int (*ptr)[10] = &arry;//ptr是一个指向含有10个整数的数组 int (&arr)[10]=arry;//arr是一个引用含有10个整数的数组 int *(&arr)[10]=arry;//arr是数组的引用,该数组含有10个指针
利用范围for,遍历数组中的所有元素:
int array[10]={12,45,12,56,9,9845,656}; for(auto c : array) cout<<c<<endl;
7.多维数组
int ia3;第一维为行,第二维为列
多维数组初始化
int id[3][4] = { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} }; int ia[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};//与上表达式相同
多维数组下标引用
int (&row)[4]=ia[2];//把row定义成一个含有4个整数的数组的引用,绑定到ia二维数组的第3行
三.表达式
1.条件运算符
条件运算符
cond ? expr1 : expr2 判断cond条件是否为真,真执行expr1,假执行expr2
嵌套条件运算符
例:
fial_grade = (grade > 90) ? "high":(grade > 60)? "pass":"false"
2.break语句
break语句负责终止离他最近的while, do while , for或swich语句
3.continue语句
终止最近的循环中的当前迭代并立即开始下一次迭代。continue只能出现在for, while 和 do while循环的内部
四.函数
1.形参和实参
实参是形参的初始值,与形参存在对应的关系,必须与对应形参类型匹配。
1. 1 形参
形参是一种自动对象(只存在于块执行期间的对象),函数开始时为形参申请存储空间,函数终止,形参也被销毁
无形参函数
void func1(); void func2(void);
有参函数
需写出每个形参的声明符
int fun(int a1,int a2,char a3)
2.局部静态对象
局部变量定义为static类型的对象,第一次定义对象后,直到程序终止才销毁。
int func1() { static int num = 0; return ++num; } int main() { for (int i=0;i < 10;i++) cout << func1() << endl; return 0; }
结果:
3.函数声明
函数只能定义一次,但可以多次声明,即重构函数
函数三要素:返回类型,函数名,形参类型
4.参数传递
4.1 传值
对形参的所有操作不影响实参,实参值不变
void fun(int a,int b) { int tmp; tmp=a; a=b; b=tmp; } int main() { int a=10,b=20; fun(a,b); cout<<"a="<<a<<"b="<<b<<endl; }
结果:
4.2 传地址
指针形参指向实参的地址,可以改变实参的值
void fun(int *a,int *b) { int tmp; tmp=*a; *a=*b; *b=tmp; } int main() { int x=10,y=20; fun(&x,&y); cout<<"x="<<x<<"y="<<y<<endl; }
结果:
4.3 传引用
通过形参通过引用方式绑定实参,改变实参的值
void fun(int &a,int &b) { int tmp; tmp=a; a=b; b=tmp; } int main() { int x=10,y=20; fun(x,y); cout<<"x="<<x<<"y="<<y<<endl; }
结果:
5.指针或引用形参与const
可以用非常量初始化一个底层const对象,但是反过来不行;一个普通引用必须用同一类型的对象初始化
const int &r = 43;//使用非常量初始化一个底层const int &r1=24;//错误,不能用字面值初始化一个非常量引用
6.main:处理命令行选项
int main(int argc,char *argv[]);
argr是一个整数,argv是指向char型指针的指针,即指向字符串的指针(指针数组)
脚本命令:prog -d -o file
即argc=4,argv[0]="prog";argv[1]="d";...argv[4]=0;
7.含可变形参的函数
为了编写能处理不同数量实参的函数,c++11新标准提供了两种方法:
-
实参类型相同
可以传递一个名为initializer_list的标准库类型,其提供的操作与vector类似
例:
void err(initializeer_list<string> li) { for(auto beg=li.begin();beg!=li.end();beg++) cout<<*beg<<endl; }
-
实参类型不同
后面介绍
8.无返回值函数
返回void的函数不要求非的有return语句,函数的最后一句会隐式执行return,若在中间位置想提前退出,课使用return语句。
9.函数重载
同一个作用域内的几个函数名字相同但形参列表不同,称为重载函数(返回值类型需相同)
int f(int i); int f(double j); f(9);//调用第一个函数 f(9.22);//调用第二个函数
9.1 重载和const形参
一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来
Record look(Phone *); Record look(Phone *const);//重复声明了Record look(Phone *) Record look(Phone); Record look(const Phone);//重复声明了Record look(Phone)
9.2 重载与作用域
在不同的作用域中无法重载函数名,内层作用域中的声明会隐藏外层作用域中的声明
void f(int i); void f(double j); void func(int val) { void f(const string c);//新作用域:隐藏了之前的f f("hello");//正确 f(2);//错误:void f(int i)被隐藏 f(2.99);//错误:void f(double j)被隐藏 }
正确使用方式:将一个函数的重载放同一个作用域中
void f(int i); void f(double j); void f(const string c); void func(int val) { void f(const string c);//新作用域:隐藏了之前的f f("hello");//正确:调用void f(const string c) f(2);//正确:调用void f(int i) f(2.99);//正确:调用void f(double j) }
10.默认实参
在函数的很多次调用中他们都被赋予一个相同的值
string func(int i=20,int j = 10,char sa = "he"); string wind; wind=func();//等价于func(20,10,"he") wind=func(12);//等价于func(12,10,"he") wind=func(2,12);//等价于func(2,12,"he")
11.函数匹配
确定某次调用应该选用哪个重载函数
void f(int); void f(int,int); void f(double,double=3.14); f(3.22);//将调用f(double,double=3.14)
分析:
-
f(int)可行,可将double转换成int
-
f(double,double=3.14)可行,第二个形参提供默认值,第一个形参为double更匹配,所以选用次重载函数
12.函数指针
指向函数的指针
12.1 使用函数指针
bool lend(const string &); pf=lend;//pf指向lend函数 bool b1=pf("hello");//调用lend函数 bool b2=(*pf)("hello");//等价的调用
12.2 重载函数的指针
void f(int *); void f(double); void (*d)(double)=f;//d指向f(double)
四. 类
基本思想:数据抽象和封装
数据抽象:一种依赖于接口和实现分离的编程技术。
1.构造函数
初始化类对象的数据成员,一个类可含有多个构造函数与重载函数类似
特点:
-
与类名相同
-
没有返回类型
1.1合成的默认构造函数
类通过一个特殊的构造函数来控制默认初始化过程,这个函数为默认构造函数,默认构造函数无需任何实参。如果类没有显式的定义构造函数,那么编译器就会隐式的定义一个默认构造函数。也称合成的默认构造函数。
初始化类的数据成员的规则:
-
如果存在类内的初始值,用它来初始化成员
-
否则,默认初始化该成员
1.2 定义构造函数
c++新标准中,使用默认构造函数,可以在参数列表后写上=default来要求编译器生成构造函数,如果=default在类的内部,则默认构造函数是内联的
class f{ f()=default; f(const std::string &sa):book(sa){} std::string book; }
不使用default则构造函数在类的外部
f::f(const std::string &sa) { book(sa); }
2.访问控制与封装
使用访问说明符加强类的封装性:
-
public:成员在整个程序内可被访问
-
protect:成员可以被类的成员函数访问
-
private:
2.1 class和struct区别
使用class和struct关键字开始类的定义
区别:struct和class的默认访问权限不同
-
使用struct关键字,定义在第一个说明符之前的成员是public
-
使用class关键字,则这些成员为private
2.2 封装的优点
-
确保用户代码不会无意间破坏封装对象的状态
-
被封装的类的具体实现细节可以随时改变,无须调整用户级别的代码
3.友元
类可以允许其他类或者函数访问他的非公有成员,方法是令其他类或者函数成为他的友元,添加一条以friend关键字开始的函数声明的语句。
4.委托构造函数
c++新标准扩展了构造函数初始值的功能,一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程。
class sale{ public: sale(std::string s,int cn,double p):book(s),sold(cn),rev(p){}//非委托构造函数使用对应的实参初始化成员 sale():sale("",0,0){}//构造函数委托给另一个构造函数初始化成员 sale(std::string s):sale(s,0,0){}//构造函数委托给另一个构造函数初始化成员 }
5.聚合类
用户可以直接访问成员,聚合类的特点:
-
所有成员都是public
-
没有定义任何构造函数
-
没有类内初始值
-
没有基类,没有virtual函数
例:
struct data{ int val; string s; } data da={0,"ame"};//初始化聚合类数据成员
6.类的静态成员
6.1 声明静态成员
在成员的声明前加关键字static使得其与类关联在一起
-
静态数据成员的类型可以是常量、引用、指针、类类型
-
静态成员函数不与任何对象绑定一起,不包含this指针,不能被声明为const的
class accout{ public: static void rate(double); private: static double interestrate; }
6.2 定义静态成员
void account::rate(double newrate) { interestrate=newrate; }
一般来说,不能在类的内部初始化静态成员,一个静态数据成员只能被定义一次。类似于全局变量,一旦定义将一直存在于程序的整个生命周期。
6.3 静态成员的类内初始化
特殊情况:静态成员const整数类型的类内初始化
要求:
-
静态成员必须是字面值常量类型的constexpr
-
初始值必须是常量表达式
class accout{ public: static void rate(double); private: static constexpr double interestrate = 30; }
6.4 静态变量的特殊作用
静态变量独立于任何对象,所有某些非静态数据成员可能非法的场合,静态成员能正常使用
6.4.1 静态变量可以是不完成类型
class bar{ static bar men;//对,静态指针可以是不完全类型 bar *men2;//对,指针成员可以是不完全类型 bar men3;//错误,数据成员必须是完全类型 }
6.4.2 使用静态成员作为默认实参
class accout{ public: static void rate(double i=interestrate); private: static double interestrate; }
五.面向对象的程序设计
三大特性:封装,继承,多态
虚函数:基类希望他的派生类各自定义适合自身的版本,此时基类就将这些函数声明为虚函数
class que{ public: virtual double func(string str) const;//const表示这个函数不能修改类里面成员变量的值 }
override:c++中新标准允许派生类显式的注明他将使用哪个成员函数改写基类的虚函数
class que1::public que{ public: double func(string str) const override; }
1.派生类
派生方式 | 基类的public成员 | 基类的protected成员 | 基类的private成员 | 派生方式引起的访问属性变化概括 |
---|---|---|---|---|
private派生 | 变为private成员 | 变为private成员 | 不可见 | 基类中的非私有成员都成为派生类中的私有成员 |
protected派生 | 变为protected成员 | 变为private成员 | 不可见 | 基类中的非私有成员在派生类中的访问属性都降一级 |
public派生 | 仍为public成员 | 仍为protected成员 | 不可见 | 基类中的非私有成员在派生类中的访问属性保持不变 |
1.1 继承与静态成员
class Base{ public: static void st(); }; class deve:public Base{ void f(); }; void deve::f() { Base::st();//对:通过Base定义了st deve::st();//对:通过deve继承了st st();//对:通过this对象访问 }
1.2 防止继承的发生
c++新标准提供一种防止继承发生的方法,即在类名后面跟一个关键字final:
class deve final { };//deve不能作为基类