C++的基本用法
C++是一种静态类型的,编译式的,通用的,大小写敏感的,不规则的编程语言,支持过程化编程,面向对象编程和泛型编程的中级语言。
C++的编译方法:
g++ runoob1.cpp runoob2.cpp -o runoob
生成的是一个可执行文件
1. typedef声明
typedef为一个已有的类型取一个新的名字:
typedef int feet;
这句话回告诉编译器,feet是int的另一个名称,所以可以用feet来声明变量。
2. 枚举类型:
如果一个变量只有几种可能的值,定义为枚举类型:
enum color { red, green, blue } c;
变量c的类型为color,只会有括号中的值
3. 变量声明和定义:
可以使用extern关键字在任何地方声明一个变量
#include <iostream>
using namespace std;
extern int a, b; // 变量声明
int main()
{
int a, b; // 变量定义
}
变量赋值的真正含义:
int count;
count = 5;
程序会首先找到一块能够储存整数的内存,将该内存单元标记为count,并将5复制到该内存单元中,然后,就可以使用count来访问该内存单元
4. bool值常量
true值为真,false值为假,不应该像c那样看成是1和0
5. lambda函数表达式:
[capture](parameters) -> return_type{body}
比如 [] (int x, int y) -> int {int z = x + y; return z + x;}
lambda表达式内可以访问当前作用域的变量,这是表达式的闭包行为。
c++传递变量有传递值和传递变量的区别,使用最前面的[capture]来指定
[ ] 表示没有定义任何变量,传进来啥就用啥
[x, &y] 表示x以传入值的方式,y以传入引用的方式
[ = ] 表示任何外部变量都以传值的方式加以引用
6. 生成随机数:
srand ( (unsigned)time(NULL)); // 设置种子
j = rand(); // 生成实际的随机数
7. 函数传参三种方式:
传值调用:
直接传入值
指针调用:
void swap(int *x, int *y); // 函数需要的参数是指针类型
swap(&a, &b); // 调用函数,传入的就是地址
引用调用:
void swap(int &x, int &y); // 函数直接使用传入参数的地址
swap(a, b); // 调用函数,传入的只是普通的变量
8. 传递数组给函数:
c++传数组给一个函数,数组类型自动转换为指针类型,所以实际传进去的都是地址
所以三种声明函数方法,都是告诉编译器将要接受一个整型指针:
void func(int *param);
void func(int param[10]);
void func(int param[]);
9. 函数返回数组:
c++不允许返回一个完整的数组,但是我们可以通过指定不带索引的数组名来返回一个指向数组的指针:
int * getarray()
{
static int r[10]; // 由于不支持在函数外返回局部变量的地址,所以定义局部变量为static
...
return r; // 返回一个指针
}
int main()
{
int * p; // 定义一个指针来接收地址
p = getarray();
}
10. c风格字符串:
char hello_array[] = "hello";
, c++中有大量的函数来操作以null结尾的字符串
strcpy(s1,s2) 复制s2到s1
strcat(s1,s2) 连接s2到s1末尾
strlen(s1) s1长度
strcmp(s1, s2) 相同返回0,s1<s2返回值小于0,反之
strchr(s1, ch) 返回一个指针,字符ch第一次出现的位置
strstr(s1, s2) 返回一个指针, 字符串s2第一次出现的位置
11. c++中的string类
string s1 = "hello";
string s2 = "world";
s2 = s1; // 复制
s3 = s2 + s1; // 拼接
len = s3.size(); // 长度
12. 数组指针
我们喜欢在程序中使用指针代替数组,因为变量指针可以递增,但是数组不能递增,因为数组是一个常量指针,所以:
int var[3] = {1,2,3};
*var = 2; // 正确
var++; // 错误,var是一个指向数组开头的常量,不能作为左值
int * ptr[3];
ptr声明为一个数组,ptr中的每一个元素都是一个指向int值的指针
指向指针的指针(多级间接寻址)
int ** ptr;
声明了一个指向int类型指针的指针
所以取值需要两个星号: ** ptr
13. C++引用
引用和指针很容易混淆,主要有三个区别:
- 不存在空引用,必须连接到一块合法的内存
- 一旦引用被初始化为一个对象,就不可以指向到另一个对象,指针可以在任何时候只想到另一个对象
- 引用必须在创建的时候被初始化,指针任何时间都可以初始化
其实变量名是变量在内存位置中的一个标签,那么可以吧引用当成是变量在内存位置中的第二个标签。
int i = 10;
int& a = i; // 这里的&读作引用,所以打印a也是10
这里就可以对照上面的函数传参方式是引用调用理解
把引用作为函数的返回值:
其实使用引用来替代指针,会是c++程序更容易维护与阅读
int& func()
{
int q;3
return q; // 错误,超出作用域
static int i;
return i; // 正确,返回一个对静态变量的引用
}
14. 结构里面的typedef关键字
普通的用sstruct来声明结构的方法就不用多说了
可以用typedef来更简单的定义结构:
typrdef struct Books
{
....;
}Books;
Books book1, book2; // 可以不用struct关键字来定义结构变量了
15. 数组的替代品:vector和array模板类
vector:
#include <vector>
using namespace std;
vector<int> vi;
int n = 3;
vector<double> vd(n);
vi的初始长度为0,vector对象在插入或者添加值的时候自动调整长度,可以使用vector包里面的各种方法
创建方式: vector<type> vt(n_elem)
array:
vector类功能强大,但是付出的代价是效率稍低,至于array对象也位于名称空间std里面,与数组一样,array对象的长度也是固定的,也使用栈也就是静态内存分配,而不是自由存储区,但是比原生数组更加方便。
#include <array>
using namespace std;
array<int, 5> ai;
array<double, 4> ad = {1.1, 1.2, 1.3, 1.4}
创建方式: vector<type, n_elem> vt
,这里与vector不一样的地方是这里的n不能是变量
小结: 无论是数组还是array vector都可以使用标准数组表示法来访问各元素,但是可以发现,array对象和数组都储存在栈中,而vector对象储存在自由存储区或者堆中。可以将一个array对象的赋给另个array对象
C++的面向对象
1. 类和对象
class Box
{
public: // 类访问修饰符,确定类成员的访问属性,还可以指定为private,protected
double length;
double hight;
double getVolumn(void);
double getVolumn(void)
{
return ...;
}
};
Box box1; // 声明类的对象就像声明基本类型的变量一样
类成员可以使用成员访问运算符(.)来访问,但是private和protected是不可以这样访问的
还可以在类的外部定义函数,使用范围解析运算符(::),但是这个需要在类的内部进行成员函数声明?
double Box::getVolumn(void)
{
return ...;
}
私有成员 private
私有成员的变量和函数在外部是不可访问的,如果没有使用任何访问修饰符,那么这个成员就默认为私有成员:
修改私有成员的方法:声明一个公有方法,然后该公有方法(不管是不是使用::在类外部定义的)就是类中的成员,可以访问类中的私有成员,所以可以用这个公有方法来修改私有成员
保护成员 protected
与私有成员十分相似,但是在该类的子类中是可以访问的
继承方式
继承方式有三种,public,protected,private,比如:
class B : public A
{
...
}
如果是protected方式继承,那么父类中成员的最高属性为protected,也就是父类中的public成员也变成了protected成员,其他成员属性因为低于或者等于protected,所以属性不变。
2. 类的构造函数和析构函数
构造函数: 会在每次创建类的新对象时执行,构造函数的名称与类的名称完全相同,不会返回人和类型,用于在对象初始化的时候为成员设置初始值
class testClass
{
...;
testClass(); // 这是构造函数,前面没有类型是因为它不会返回任何类型,也不会返回void
~testClass(); // 这是析构函数
}
testClass::testClass(void) // 构造函数也可以带有参数
{
...;
}
使用初始化列表来初始化字段:
怎么给对象里面的字段给初始值呢?可以使用初始化列表来初始化字段
testClass::testClass(double len): length(len)
{
...
}
testClass test(1);
testClass类有length这个成员,那么我们想给length这个成员赋予初始值,可以在创建对象的时候赋予,最后一行的时候传入了参数1,那么len=1,并且同时length=len=1。
析构函数
与构造函数区别只有,前面需要加波浪号~,然后用处是在删除所创建对象时执行
testClass::~testClass(void)
{..}
3. 类的拷贝构造函数
这个特殊函数的作用是在创建对象的时候,使用同一类中之前创建的对象来初始化新的对象,所以作用之一就是复制对象把它作为参数传递给函数,常见形式:
classname (const classname &obj){
}
函数参数里面出现了&,也就是引用,所以obj是一个对象引用,用于初始化另一个对象,实际例子:
class Line
{
public:
int getLength(void); // 普通的成员函数
Line(int len); // 构造函数
Line(const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int *ptr;
};
Line::Line(int len) // 构造函数
{
ptr = new int; // 为指针分配内存
*ptr = len; // 内存里面存初始化对象时传入的值
}
Line::Line(const Line &obj) // 拷贝构造函数
{
ptr = new int; // 为自己的指针分配内存
*ptr = *obj.ptr; // 拷贝值
}
Line::~Line(void) // 析构函数
{
delete ptr; // 释放为指针分配的内存
}
int main()
{
Line line1(30);
Line line2 = line1; // 这里实际上调用了拷贝构造函数,用一个已有对象初始化了一个一样的对象
return 0;
}
4. 友元函数
类的友元函数是定义在类外部,但有权限访问类的所有成员,包括private和protected。友元函数不是类的成员函数。
- 要声明函数为一个类的友元,在类中定义函数原型前使用friend关键字
class Box{
friend void func(Box box);
};
void func(Box box){ // 它不是任何类的成员函数
...
}
- 如果声明一个类中的所有成员函数都是另一个类的友元函数,需要在另一个类比如是ClassOne中加上声明:
class ClassOne{
friend class ClassTwo; // 这样ClassTwo中的所有函数都能使用ClassOne中的私有或者保护成员了。
}
5. 内联函数
如果一个函数是内联函数,那么在编译的时候,编译器会把该函数的代码副本放置在每个调用该函数的地方。所以如果内联函数有任何修改,都需要重新编译。内联函数一般是1-5行的小函数:
inline int Max(int x, int y) // 内联函数就是函数开头使用inline说明符
{
return (x > y) ? x : y;
}
6. this指针和指向类的指针
C++中每一个对象都能使用this指针来访问自己的地址,所有在成员函数内部,可以使用this指针来指向调用对象。友元函数没有this指针,因为友元函数不是类的成员函数
class Box{
double Volumn(){
return 1;
}
int compare(Box box){
return this->Volumn() > box.Volumn();
}
};
指向类的指针:
指向类的指针和指向结构的指针非常类似,也是用->符号来访问成员的
int main(void)
{
Box box1(1, 2, 3);
Box *ptr;
ptr = &box1;
ptr->Volumn(); // 使用成员访问符访问成员
}
7. 类的静态成员
当我们声明类的成员为静态的时候,意味着无论创建多少个类的对象,静态成员都只有一个副本,所以静态成员在所有类的对象中都是共享的。
class Box{
public:
static int objcount;
Box(double a, double b, double c){
objcount++;
}
};
int Box::objcount = 0;
int main(void){
Box box1(1,2,3);
Box box2(1,2,3);
cout << Box::objcount;
return 0;
}
上面的输出objcount就是2了,静态函数只用使用类名加上::就可以访问
8. 类的继承
已有类为基类,新建的继承的类为派生类
class new_class: access_specifier base_class
access_specifier是访问修饰符,也是public,protected,private中的一个,如果不指定,默认是private。
派生类可以刚问基类中的所有非私有成员。一个派生类继承了所有基类的方法,除了基类的构造函数,析构函数,拷贝构造函数,友元函数,重载运算符。
9. 重载运算符 重载函数
C++允许在同一个作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算重载符
同一个作用域中,可以声明几个功能类似的同名函数,这些同名函数的形式参数必须不同(个数,类型,顺序)。编译器会把传入的参数类型和定义中的参数类型进行比较,决定选用最合适的定义,这个选择的过程叫重载决策。
class printData{
public:
void print(int i){
cout << i;
}
void print(double f){
cout << f;
}
};
10. 多态
多态意味着调用成员函数的时候,会根据调用函数的对象的额类型来执行不同的函数。
比如有一个类:
class Shape{
public:
int area(){
out << ...
}
};
然后这个shape类有两个派生类,里面都重写了area()函数,但是初始化为对象实际调用函数的时候,但是会调用这个基类的函数,这就是所谓的静态多态,函数调用在程序执行前就准备好了,也被称之为早绑定,该函数在程序编译的时候就已经设置好了
然后我们需要在函数声明前放置关键字virtual:
virtual int area(){...}
此时,编译器看的是指针的内容,而不是它的类型。
虚函数: 虚函数就是在基类中使用virtual关键字定义的函数,它会告诉编译器不要静态链接该函数,所以会动态链接
纯虚函数: 如果想要实现虚函数的功能,但是在基类中又不能给出该函数实际的意义,那么可以用纯虚函数:
virtual int area() = 0;
11. C++接口,抽象类
C++接口使用抽象类来实现的,如果一个类中至少有一个函数被声明为纯虚函数,那么这个类就是抽象类。
抽象类是为了给其他类提供一个可以继承的适当的基类,抽象类不能被用于实例化对象,只能作为接口使用。
C++的目标之一就是将C语言转变成OOP语言。
标准的C++程序:
#include <iostream>
int main()
{
using namespace std;
cout << "Come up and C++";
cout << endl;
cout << "You ..." << endl;
return 0;
}
第一行的include为了实现输入输出,后面的using指令使得std命名空间的所有名称可用,而不必使用std::前缀。
<<
符号将一个字符串插入到了输出流中,endl
表示重启一行
cout << variable;
可以直接打印出variable的值
cin >> variable;
将键盘输入插入到输出流中
字符串常量:使用双引号,比如“s”代表的是字符s和\0组成的字符串
字符常量:使用单引号,比如’s’只是83的另一种写法
使用cin输入的时候,cin不能识别空格,所以我们需要使用cin.getline(name, 20)
来将输入读入到一个包含20个元素的name数组里面去。
string类:
c++可以使用string类型的变量了,而不是继续用数组来储存字符串
string str2 = "panther";
类设计能够自动处理string的大小,将自动调整string的长度
str3 = str1 + str2
可以使用+将两个string对象合并起来
int len1 - str1.size()
使用对象的方法来计算字符串的长度
指针和自由存储空间
计算机储存数据必须跟踪三个基本属性:信息储存在哪里,储存的值是多少,储存的信息时什么类型。
&和*都是沿用了C的风格
分配和释放内存:
int * ps = new int; // 用new来分配一个int的内存
... // 使用内存
delete ps; // 释放内存
delete是释放ps指向的内存,但是不会删除指针本身,可以将ps重新指向一个新分配的内存块,所以要配对的使用new和delete,否则将会发生内存泄漏
动态数组:
- 使用new创建动态数组
int * psome = new int [10];
delete [] psome; // 加上[]是因为要释放整个数组,而不是指针指向的数组第一个元素
- 使用动态数组
如何访问里面的元素呢?可以使用psome[0],C和C++的数组和指针基本等价,所以只要把指针当做数组名就可以