C++基础回顾(上)
目录
- C++基础回顾(上)
- 前言
- 关键字和标识符
- 运算符
- 数据类型
- 函数
- 类
前言
C++之前学过一点,但是很长时间都没用过,翻出了书从头看了一遍,简短地做了笔记,以便自己之后查看和学习。
关键字和标识符
C++标识符是由字母、数字、下画线组成的,而且必须以字母或下画线开头
程序的约定俗称:
-
对象名一般用小写字母;
-
单词之间可用下划线或者每个单词首字母大写
字符型char用来保存机器基本字符集中对应的整数值,即该字符的ASCII码值
访问一个对象所在内存空间中的数据,需要知道其内存空间所在的内存地址,这通过对象的名字来实现。也就是说对象名本质上是内存地址的一个映射。
创建对象时最好提供一个初始值,以完成初始化。使用未初始化的对象是很危险的,有可能引起严重错误。
const修饰符
-
不希望对象的内容发生变化
-
const对象创建后,其值不能再改变,因此const对象必须初始化。
typedef
typedef double price //price 是double的一个类型别名
using 关键字也可以声明别名
using price = double; //price 是double的一个类型别名
auto 关键字被利用来根据初始值的类型自动推导出需要的数据类型
左值所在的内存空间的地址是可以获取的,而右值的地址是无法得到的。
运算符
<<
输出运算符
>>
输入运算符
自增和自减运算符
int i=0;
i++;
++i; //这两个的作用都等同于i=i+1
//但是他们放到表达式中就不同了
int j;
j=i++;//先赋值,再自增,即j=0,i=1了
j=++i; //先自增,再赋值,即j=1,i=1了
//自减运算符类似
符号优先级:逻辑非最高,其次是关系运算符,最后是逻辑与和逻辑或
条件运算符?:
三目运算符
cond?expr1:expr2
判断cond表达式,真则返回expr1,假则返回expr2
{
} //用花括号括起来的语句是复合语句,构成一个语句块,在块中引入的名字只能在块内可见。
数据类型
复合类型: 指针、引用、数组、函数、联合体、枚举类型
引用:
-
为已经创建的对象重新起一个名字,编译器只是将这个别名绑定到所引用的对象上,不会复制对象的内容给引用。
-
相当于还是那一块内存,只不过这个内存的名字多了,比如小明还可以叫明明。
int counter=0; int &refCnt =counter; //refCnt引用counter对象的内容
-
右值引用:C++11新引入。可以操控右值对象了,尤其是编译器产生的临时对象
int i=0; int &&rr1=i+1; //右值引用,绑定到一个临时对象,临时对象的生命期得到了延续,续命了。
指针:
-
存放的是数据的内存地址,然后通过这个对象对数据进行访问。这种专门用来存放地址的对象称为指针对象。
int i =100; int *ptr=&i; //&在这里是取址符 现在ptr对象里存放的就是i的地址。
-
要访问i的内容,需要通过解引用操作符*来实现
*ptr=10; //读取对象i的内容
-
避免使用野指针,如果指针对象在定义时没有具体的指向对象,则需要用nullptr来初始化。
{ int *ptr1 =nullptr; //空指针 int *ptr; //野指针 }
-
多级指针
把一个指针对象的地址存放到另一个指针对象中,则形成了指向指针的指针
int i=1, *ptr =&i; int **pptr =&ptr; //指向指针的指针,pptr中存储的是ptr的地址,而ptr中存放的是i的地址
-
引用可以看作是支持自动解引用操作的const指针
数组
-
数组是有限个同类型元素组成的有序集合,所有元素顺序存放在一段连续的内存空间中。
int arr[5] //存储5个整型元素的数组
-
[]中的值必须是大于0的整型常量表达式
unsigned cnt =10; int arr[cnt]; //不能这样定义,cnt并不是常量表达式 constexpr int sz =10; int arr[sz]; //可以
-
字符数组会在末尾自动添加字符串结束符’\0’
-
举例
inr arr[5] ={1,2,3,4,5}; for (auto i:arr){ //auto自动推断数据元素的类型 cout<< i<<endl; } //这里只能进行读操作,因为i只是一个临时对象,他与数组arr并没有产生实际上的联系,我觉得 //可以进行写操作的例子 for (auto &i:arr){ i=0; } //此时i是引用了,相当于arr的别名,映射的地址都是相同的,因此对i赋值,能够修改arr的内容
-
多维数组
int a2d[3][5]; //二维数组 3*5
-
指针指向数组
一般情况下,编译器对数组的操作都会转换成对指针的操作,数组名通常被转换成数组第一个元素的地址
int arr[] ={1,2,3,4,5}; int *p =arr; //arr被转换成arr[0]的地址 int *p =&arr[0]; //与上一句等价
-
可以用指针访问数组,比如移动指针位置,从而访问数组中的其他数据。但要注意,指针运算过程中不能超出数组的范围了,不然就不知道指向的地址是哪里了。
vector
-
容器类型,能够存放类型相同的元素,但是支持变长操作,对容量大小可根据需要进行动态调整。
-
定义和初始化
vector<int> v1; //存放整数的空vector vector<int> v2={0,1,2}; //存放三个int元素的vector
-
vector<int> vi; vi.push_back(); //尾部插入一个元素 vi.pop_back(); //尾部删除一个元素 vi.clear(); //移除所有元素 vi.at(1) //访问vi的第二个元素
-
迭代器
为了支持随机访问, vector中有一个叫迭代器的东西,通常借助容器的成员函数begin和end获取
vector<int> vi={0,1,2,3}; auto itb=vi.begin(); //itb指向vi的第一个元素 auto ite=ci.end(); //ite指向vi的尾后元素 //itb和ite都是vector<int>::iterator的迭代器类型
迭代器与指针类似,可以通过解引用来获取指向对象的内容。
for (auto it=vi.begin();it!=vi.end();++it){ *it *=2; //每个元素乘2 cout<<*it<<endl; }
枚举类型
-
不限定作用域和限定作用域方式的枚举类型
-
不限定作用域的枚举类型和限定作用域的枚举类型
enum color{red,green,blue}; //enum关键字来定义不限定作用域的枚举类型 enum class stoplight{red,green,yellow}; //enum class 来定义限定作用域的枚举类型 color c =red; //访问color的枚举成员,限定作用域的不能这样访问了 stoplight b =stoplight::red; //访问限定作用域的枚举类型 stoplight a =red; //不行
函数
一个函数的定义由四部分组成:返回值类型、函数名、形参列表和函数体。错误分为语法错误和逻辑错误。
int main(){
return 0;
}
函数调用过程中,相对应的实参类型和形参类型必须匹配。
和对象的名字一样,函数的名字必须在使用之前声明。和函数定义不同,函数声明只需要描述函数的返回类型、名字和形参类型即可。
int maximum(int a ,int b); //因为声明没有函数体,因此形参名称也是可以省略的
int maximum(int, int);
一般来说,函数或对象声明放在头文件,相应的定义放在源文件。
局部对象和全局对象
存储周期:对象在内存中存储的时间
存储周期类型 | 作用周期 | 存储位置 |
---|---|---|
自动存储周期 | 定义位置在内存中创建,离开作用域时释放 | 栈 |
静态存储周期 | static关键字声明的对象,程序运行期间,始终存在 | 全局数据区 |
动态存储周期 | new运算符生成的对象,delete释放内存空间 | 堆 |
线程存储周期 | thread_local关键字创建的对象,在其所在的线程创建时开始,线程结束时释放 |
全局对象具有外部链接性,可以在其他的文件中访问
int g_val =10; //g_val在fun.cpp中定义的全局对象,具有外部链接性
extern int g_val; //extern声明可以使得main.cpp也能访问到g_val
static int gs_val=20; //静态对象,在外部访问不了
int main(){
cout <<g_val+gs_val;
} //输出30
参数传递
函数的实参和形参的交互方式:单向的值传递,双向的引用传递
单向的值传递中,函数的形参发生变化,不会影响到实参
void Swap(int x,int y){
int z(x);
x=y;
y=z;
}//形参x y交换
int i(4),j(5);
Swap(i,j);
cout<<i<<j<<endl; //实参 i j的内容并没有被改变
地址传递,可以通过形参改变实参的值。
void Swap(int *x,int *y){
int z(*x);
*x=*y;
*y=z;
}//形参x y所指向的地址的内容交换
int i(4),j(5);
Swap(&i,&j); //取址符
cout<<i<<j<<endl; //实参 i j的内容交换了
引用传递,类似地址传递
void Swap(int &x,int &y){ //引用,即别名
int z(x);
x=y;
y=z;
}//形参x y内容交换
int i(4),j(5);
Swap(i,j);
cout<<i<<j<<endl; //实参 i j的内容交换了
const形参
const形参好像在程序应用很广泛
void fun(const int i); //只可以读i,不可写
void fun(const int *i); //可以更改i的指向,但是改不了指向的地址的内容
void fun(const int &i); //只读,不可写
上述值传递对于实参是安全的,但是对于数据大的对象复制操作低效;引用可以避免复制,但是实参不安全;const 引用形参安全高效。
向main函数传递实参
//main函数有两个可选的形参
int main(int argc,char*argv[]){
}
//第一个形参argc的值是argv的元素的个数,第二个形参是一个数组,每个元素是指向C风格字符串的指针
//argv[0]保存的是可执行程序的名字,可选实参从argv[1]开始
函数重载
-
统一作用域下具有相同名字,但是不同形参列表的一组函数称为重载函数
const int & getMax(const int &a,const int &b){} //这里的值返回的是一个int常量的引用 const int & getMax(const int &a,const int &b, const int &c){} //重载 const string & getMax(const string &a,const string &b){} //重载
默认函数
void turnoff(int time =21); //声明的时候给出了默认形参,调用时不提供实参,则采用默认值21
内联函数
inline void Swap(int &x,int &y){}; //inline关键字定义内联函数,直接在调用处嵌入内联函数代码,不发生函数调用,不产生函数调用开销,但对编译器只是建议
lambda表达式
-
lambda表达式可以理解为一个临时的匿名函数,表示一个可以调用的代码单元
[captures](parameters)-> return type{statements} //parameters 、return type 、statements分别代表形参列表、返回值类型和函数体 //[]代表lambda引导,captures子句指定在同一作用域下lambda主体访问哪些对象以及如何捕获这些对象,可以为空
int divisor=5; vector <int > numbers{ 1,2,3,4,5,10,15,20,25,35,45,50); for each(numbers,begin(),numbers.end(),[divisor](int y){ if(y%divisor==0) //divisor 为外围divisor 的副本 cout <<y <<endl;//输出被 divisor整除的元素 });
宏定义
-
功能是定义一个标识符来代替一串字符,该标识符为宏名
-
#define 宏名 字符串常量 #define PI 3.1415926
类
-
类是用户自定义类型数据,基本思想是抽象和封装
-
类是对一个事物的属性和操作的描述。例如对于一个分数类来说,基本属性有分子和分母,操作包括约分、计算分数值等操作。
class Fraction{ //Fraction是类名 有两个数据成员和5个成员函数 //数据成员 int m_fenzi=0; //分子,默认值为0 int m_fenmu=1; //分母,默认值为1 public: //public关键字后的成员时对外公开的,在程序的任何地方都可以访问 //成员函数 int fenzi() const{return m_fenzi;} //这里在圆括号后面引入const关键字是说明对this指针指向的const对象的数据成员进行写操作是非法的。 int fenmu() const{return m_fenmu;} double value() const; //计算分数值 void reduce(); //约分 private: // private 私有成员函数,只能在类的内部使用 //值得注意的是,不显式指明成员的访问属性,访问属性默认private int gcd(int x, int y); //计算x和y的最大公约数 }; //类中只有成员函数的声明,定义最好放在类外面
double Fraction::value() const{ return static_cast<double> (m_Fenzi)/m_fenmu; } //成员函数value 的定义
Fraction a; a.value(); a.value(&a); //这两句是等价的,意思是当通过对象调用成员函数时,有一个隐式的指针类型形参this接受了调用对象的地址 double value(Fraction *const this)const; //value 成员函数的声明自动转换为该形式,一个隐式的指针类型形参this
struct关键字也可以用来定义一个类,使用struct定义中的类成员的访问属性默认为public
-
友元函数
不是类的成员却可以访问类的非公有成员。
class Fraction{ friend ostream &print(ostream&os, const Fraction &a); //friend关键字声明友元函数 };
-
友元类
class cricle; //类的前向声明 class rectangle{ friend class cricle; //友元类 }; //友元都是单向传递的,你是我的朋友,但我不是你的朋友
-
构造函数与析构函数
类类型对象的初始化过程是由一类特殊的成员函数完成的,称为构造函数
构造函数帮助对象创建时为数据成员执行初始化操作,只要创建类类型对象就会执行构造函数
构造函数:1. 函数名和类名一致;2. 无返回值;3. 不能声明为const成员函数
class Fraction{ public: Fraction()=default; //默认构造函数 Fraction(int above, int below):m_fenzi(above),m_fenmu(below){} //显示定义构造函数,但是也是默认构造函数 Fraction(int above, int below){ m_fenzi=above; m_fenmu=below; }//这一个等同于上一个构造函数,但是上一个在m_fenzi和m_fenmu创建时就初始化了,这个还要再赋值一下,上一个执行效率更高。 };
析构函数
class Fraction{ public: ~Fraction(){} //析构函数 };
-
静态成员:可以通过static关键字声明静态成员,但该成员是共享的,类对象不包含静态成员
class Fraction{ pulic: static int num; }; Fraction a ;//a成员中不包含num这个成员
如果您觉得我写的不错,麻烦给我一个免费的赞!如果内容中有错误,也欢迎向我反馈。