这是我的C++学习笔记第一篇,同所有的程序语言学习路径一样,首先学习的是变量和数据类型。
我的学习路径如下:
1. 变量和数据类型 2. 流程控制 3. 函数声明和调用 4. 面向对象 5. 标准库
这一章,学习的是变量和数据类型,需要了解的有:
1. 变量怎么定义?
2. 常见的都有哪些数据类型,占用的内存是多少?
3. 变量的作用域都有哪些?
4. 变量的生命周期是什么?
了解这些,对于变量基本就够了。
Hello world
在开始之前,先写一个hello world来熟悉一下程序的主要结构以及如何打印一个变量。
#include <iostream>
int main () {
std::cout << "hello world" << std::endl;
return 0;
}
iostream提供标准输入输出的头文件,程序以main函数问入口,std为标准库的命名空间,“<<” 为输出操作符,std::cout为标准输出,std::endl为结束符,表示将等待输出的内容从内存传送到标准输出。
使用gcc编译,假设上面的文件为hello.cpp
g++ hello.cpp
mac上将会产出a.out文件,通过 ./a.out 就可以执行。
变量的声明和定义
C++变量定义为
类型 变量名;
声明一个变量
extern int i;
变量不初始化即为声明,初始化之后即为定义。变量可以被声明无数次,但是只能被定义一次。
定义一个整型变量:
int i;
i = 1; // 给i赋值
还可以同时定义多个变量,比如
int i=0, j, k=1
// i,j,k 都为int类型,j没有初始化,初值为0
定义变量可以带有一些限定符,比如可以在定义变量前加static,const等。
变量的基本类型
类型 | 名称 |
bool | 布尔 |
char | 字符 |
int | 整型 |
float | 浮点型 |
double | 双精度浮点 |
void | 无类型 |
char, int, float, double 可以通过 signed, unsigned, short, long这些类型修饰符来修饰。
注:变量所占内存的大小和系统的编译器有关。
const 限定符
在定义变量的前面加一个const,表示这个变量的值不能被改变,默认状态下,const定义一个变量的时候,这个变量只在该文件内有效。
const int i = 1;
i = 2; // 错误,修改了这个变量
如果在多个文件中需要共享这个变量,那么需要加extern关键词。
// a文件定义
extern const i = 1;
// b文件使用,此处i为1
extern const i;
当const和指针在一起的时候,问题就变得比较复杂,学习的时候也会比较头痛(我就是这样)。const和指针在一起有两种情况,一种是指向常量的指针,另一种是常量指针。指向常量的指针用 const int *ptr 形式定义,常量指针用 int *const ptr 定义。
指向常量的指针想要表示的是这个指针指向了一个常量,我们不能通过这个指针来更改所指向的值(但其实指向常量的指针我们可以将它指向一个非常量),通过下面的例子可以演示指向常量的指针是什么样子的:
const int i = 1;
const int *ptri = &i;
std::cout << *ptri << std::endl;
// 尝试修改,就会出错,编译不会通过
*ptri = 2;
// 实际上我们还可以将指向非常量,一点也不会出错
int j = 2;
ptri = &j;
std::cout << *ptri << std::endl; // 这时候打印的是j
// 虽然j不是常量,但是ptri以为自己指向了一个常量,会禁止修改,下面的语句依然会出错
*ptri = 2;
常量指针想要表示的是这个指针本身是一个常量,也就说说这个指针是不能被修改的。看下面的代码
int j = 0;
// 常量指针 ptr 将一直指向j
int *const ptr = &j;
// 尝试修改指向将会出错,但是可以修改指针的值
// ptr1是一个指向常量的常量指针
const int k = 1;
const int *const ptr1 = &k
// 尝试修改指针指向和修改指针的值都会出错
变量的初始化
C++中,变量的初始化是一个异常复杂的问题。(看到各种初始化的方式,不用说也能感觉到非常复杂,至于内部为什么如此复杂,暂时不研究)。
当我们语句
int age = getAge();
的时候,我们用的是getAge返回值去初始化age这个变量。咋一看,用等于号似乎是在给age赋值,实际上这是初始化。在很多语言中这两者都差不多,但在C++中,初始化和赋值是两个完全不同的操作。
初始化不是赋值,初始化的含义是创建变量时候赋予一个初始值,而赋值的含义是把对象的当前值擦除,以一个新的值替代。
初始化方式
下面四种语句都可以将age初始化为20。
int age = 20;
int age = {20};
int age{20};
int age(20);
使用花括号的初始化方式称为列表初始化,已经称为C++11标准的一部分。用花括号初始化这种方式有一个特点:初始值存在丢失的风险,编译器将会报错,比如:
long double ld = 3.1415926;
int a{ld}, b = {ld}; //错误,转换未执行,存在丢失信息的风险
int a(ld), b = (ld); //正确,转换执行,确实丢失了部分值
变量的作用域
C++中绝大多数的作用域都以花括号分隔。其中有全局作用域,块作用域。
全局作用域:在所有花括号外面定义的变量
块作用域:在某个花括号内部定义的变量
#include <iostream>
int main() {
int i = 0;
for (int i=0; i<5; i++) {
// i为块作用域,只在花括号内部生效
std::cout << i << std::endl;
}
// 外部的i仍为0
std::cout << i << std::endl;
}
// 输出
// 0
// 1
// 2
// 3
// 4
// 0
作用域可以嵌套。
变量的生命周期
全局变量:作用域为全局的生命周期为整个程序的生命周期,伴随着程序的启动而存在,程序的停止而消亡。
局部变量:作用域为局部的变量生命周期为某局部运行时间,程序运行出局部该变量即被销毁。变量存储在栈区。
const限定的变量:生命周期在整个程序运行的时候都存在,和全局变量一致
复合类型
复合类型是基于其他类型定义的类型,C++中有两种比较重要的复合类型,引用和指针。
引用:引用实际上是一个别名,只是为一个已经存在的对象所起的另外一个名字。引用声明方式为 类型 &标识符
, 例如:
#include <iostream>
int main() {
int i = 1;
int &ref = i;
std::cout << i << std::endl;
// 给引用赋值,实际上就是给i赋值
ref = 2;
std::cout << i << std::endl;
return 0;
}
定义引用的时候会把引用和初始值绑定在一起,而不是赋值值,引用无法重新绑定另一个对象。引用不是对象,无法定义引用的引用。
指针
指针是指向另外一种类型的复合类型,保存的是变量的地址,定义方式是
类型 *变量
#include <iostream>
int main() {
int i = 1;
int *p = &i;
std::cout << *p << std::endl;
return 0;
}
使用解引用符(符号*)来访问指针指向的对象。上面的std::cout << *p << std::endl语句输出的就是i的值,也就是1。
字符串
在C里面,字符串是一个char数组,比如
char str[] = "hello"
std::cout << str << std::endl
// 将会直接输出hello
不过,C++标准库中提供了一个string类来让字符串的使用更加方便。
#include <iostream>
#include <string>
using namespace std;
int main () {
string str1 = "hello";
string str2 = "world";
cout << str1 + str2 << endl;
}
string字符串类可以很方便地进行连接,替换操作。
结构体
结构体可以让我们自定义我们想要的数据结构,定义结构体的方法是
struct 结构体名称 {
成员类型 成员名;
...
}
比如我们定义人这个结构体
struct Human {
string name;
int age;
int sex;
}
结构体通过 “.” 访问结构体成员,比如下面的程序
#include <iostream>
#include <string>
using namespace std;
int main() {
struct Hunam {
string name;
int age;
int sex;
};
Hunam cmhc;
cmhc.name = "hello";
cout << cmhc.name << endl;
}
容易混淆的点
由于C++的语法比较复杂,很多地方容易混淆。
*和&
*和&可以在表达式里面左运算符,也可以作为声明的一部分,符号的上下文决定了决定了符号的意义。整理如下
int i = 42;
int &r = i; // 这里的&出现在声明部分,表示r是一个引用。
int *p; // *出现在声明部分,表示p是一个int指针
p = &i; // 这里的&出现在表达式里面,表示去地址符
*p = i; // 这里的*出现在表达式中,表示解引用
int &r = *p; // &出现在声明部分,表示r是一个引用,*出现在表达式部分,表示解引用
声明语句中*和&是什么
*和&是类型修饰符,修饰的是定义的那个变量,但是和类型挨着或者和变量挨着都是合法的(这具有迷惑性)
// 这两条语句是等价的,*挨着谁都无所谓
int *i;
int* i;
把*靠近类型的写法具有一些迷惑性,比如
int* i,j;
看起来i和j似乎都是指向int指针,但是实际上j不是,还是上面说的,*修饰的是变量,而不是类型。在上面的语句,*仅仅修饰了i而没有修饰j.
你可能还喜欢下面这些文章
赞赏微信赞赏
支付宝赞赏